aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-02 17:48:50 +0200
committerChocobozzz <me@florianbigard.com>2018-08-06 11:19:16 +0200
commited31c059851a30bd5ba9999f8ecb3822d576b9f4 (patch)
tree7923ef7891ae9fd26965c3004bd75e736f0fedd0
parent299474e8279675adb6c5ce140e7e39c6f3439453 (diff)
downloadPeerTube-ed31c059851a30bd5ba9999f8ecb3822d576b9f4.tar.gz
PeerTube-ed31c059851a30bd5ba9999f8ecb3822d576b9f4.tar.zst
PeerTube-ed31c059851a30bd5ba9999f8ecb3822d576b9f4.zip
Add ability to list video imports
-rw-r--r--client/src/app/+my-account/my-account-routing.module.ts10
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html37
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss2
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts66
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts2
-rw-r--r--client/src/app/+my-account/my-account.component.html2
-rw-r--r--client/src/app/+my-account/my-account.module.ts8
-rw-r--r--client/src/app/shared/video-import/video-import.service.ts38
-rw-r--r--server.ts2
-rw-r--r--server/controllers/api/users.ts30
-rw-r--r--server/helpers/youtube-dl.ts2
-rw-r--r--server/initializers/constants.ts1
-rw-r--r--server/lib/job-queue/handlers/video-import.ts2
-rw-r--r--server/middlewares/validators/sort.ts3
-rw-r--r--server/models/video/video-import.ts82
-rw-r--r--server/models/video/video.ts8
-rw-r--r--shared/models/videos/video-import.model.ts7
17 files changed, 283 insertions, 19 deletions
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts
index 91b464f75..6f0806e8a 100644
--- a/client/src/app/+my-account/my-account-routing.module.ts
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -8,6 +8,7 @@ import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.
8import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component' 8import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
9import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' 9import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
10import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' 10import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
11import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
11 12
12const myAccountRoutes: Routes = [ 13const myAccountRoutes: Routes = [
13 { 14 {
@@ -64,6 +65,15 @@ const myAccountRoutes: Routes = [
64 title: 'Account videos' 65 title: 'Account videos'
65 } 66 }
66 } 67 }
68 },
69 {
70 path: 'video-imports',
71 component: MyAccountVideoImportsComponent,
72 data: {
73 meta: {
74 title: 'Account video imports'
75 }
76 }
67 } 77 }
68 ] 78 ]
69 } 79 }
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
new file mode 100644
index 000000000..74ca33fa3
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
@@ -0,0 +1,37 @@
1<p-table
2 [value]="videoImports" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
4>
5 <ng-template pTemplate="header">
6 <tr>
7 <th i18n>URL</th>
8 <th i18n>Video</th>
9 <th i18n style="width: 150px">State</th>
10 <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
11 <th></th>
12 </tr>
13 </ng-template>
14
15 <ng-template pTemplate="body" let-videoImport>
16 <tr>
17 <td>
18 <a [href]="videoImport.targetUrl" target="_blank" rel="noopener noreferrer">{{ videoImport.targetUrl }}</a>
19 </td>
20
21 <td *ngIf="isVideoImportPending(videoImport)">
22 {{ videoImport.video.name }}
23 </td>
24 <td *ngIf="isVideoImportSuccess(videoImport)">
25 <a [href]="getVideoUrl(videoImport.video)" target="_blank" rel="noopener noreferrer">{{ videoImport.video.name }}</a>
26 </td>
27 <td *ngIf="isVideoImportFailed(videoImport)"></td>
28
29 <td>{{ videoImport.state.label }}</td>
30 <td>{{ videoImport.createdAt }}</td>
31
32 <td class="action-cell">
33 <my-edit-button *ngIf="isVideoImportSuccess(videoImport)" [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button>
34 </td>
35 </tr>
36 </ng-template>
37</p-table>
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
new file mode 100644
index 000000000..5e6774739
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
@@ -0,0 +1,2 @@
1@import '_variables';
2@import '_mixins';
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
new file mode 100644
index 000000000..31ccb0bc8
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
@@ -0,0 +1,66 @@
1import { Component, OnInit } from '@angular/core'
2import { RestPagination, RestTable } from '@app/shared'
3import { SortMeta } from 'primeng/components/common/sortmeta'
4import { NotificationsService } from 'angular2-notifications'
5import { ConfirmService } from '@app/core'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { VideoImport, VideoImportState } from '../../../../../shared/models/videos'
8import { VideoImportService } from '@app/shared/video-import'
9
10@Component({
11 selector: 'my-account-video-imports',
12 templateUrl: './my-account-video-imports.component.html',
13 styleUrls: [ './my-account-video-imports.component.scss' ]
14})
15export class MyAccountVideoImportsComponent extends RestTable implements OnInit {
16 videoImports: VideoImport[] = []
17 totalRecords = 0
18 rowsPerPage = 10
19 sort: SortMeta = { field: 'createdAt', order: 1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21
22 constructor (
23 private notificationsService: NotificationsService,
24 private confirmService: ConfirmService,
25 private videoImportService: VideoImportService,
26 private i18n: I18n
27 ) {
28 super()
29 }
30
31 ngOnInit () {
32 this.loadSort()
33 }
34
35 isVideoImportSuccess (videoImport: VideoImport) {
36 return videoImport.state.id === VideoImportState.SUCCESS
37 }
38
39 isVideoImportPending (videoImport: VideoImport) {
40 return videoImport.state.id === VideoImportState.PENDING
41 }
42
43 isVideoImportFailed (videoImport: VideoImport) {
44 return videoImport.state.id === VideoImportState.FAILED
45 }
46
47 getVideoUrl (video: { uuid: string }) {
48 return '/videos/watch/' + video.uuid
49 }
50
51 getEditVideoUrl (video: { uuid: string }) {
52 return '/videos/update/' + video.uuid
53 }
54
55 protected loadData () {
56 this.videoImportService.getMyVideoImports(this.pagination, this.sort)
57 .subscribe(
58 resultList => {
59 this.videoImports = resultList.data
60 this.totalRecords = resultList.total
61 },
62
63 err => this.notificationsService.error(this.i18n('Error'), err.message)
64 )
65 }
66}
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
index 54830c75e..01e1ef1da 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
@@ -145,6 +145,8 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
145 suffix = this.i18n('Waiting transcoding') 145 suffix = this.i18n('Waiting transcoding')
146 } else if (video.state.id === VideoState.TO_TRANSCODE) { 146 } else if (video.state.id === VideoState.TO_TRANSCODE) {
147 suffix = this.i18n('To transcode') 147 suffix = this.i18n('To transcode')
148 } else if (video.state.id === VideoState.TO_IMPORT) {
149 suffix = this.i18n('To import')
148 } else { 150 } else {
149 return '' 151 return ''
150 } 152 }
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html
index 48db55ad3..f67245d85 100644
--- a/client/src/app/+my-account/my-account.component.html
+++ b/client/src/app/+my-account/my-account.component.html
@@ -5,6 +5,8 @@
5 <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a> 5 <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a>
6 6
7 <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a> 7 <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
8
9 <a i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My video imports</a>
8 </div> 10 </div>
9 11
10 <div class="margin-content"> 12 <div class="margin-content">
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 2088273e6..5403ab649 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -1,3 +1,4 @@
1import { TableModule } from 'primeng/table'
1import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
2import { SharedModule } from '../shared' 3import { SharedModule } from '../shared'
3import { MyAccountRoutingModule } from './my-account-routing.module' 4import { MyAccountRoutingModule } from './my-account-routing.module'
@@ -11,11 +12,13 @@ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-vid
11import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' 12import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
12import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' 13import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
13import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' 14import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
15import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
14 16
15@NgModule({ 17@NgModule({
16 imports: [ 18 imports: [
17 MyAccountRoutingModule, 19 MyAccountRoutingModule,
18 SharedModule 20 SharedModule,
21 TableModule
19 ], 22 ],
20 23
21 declarations: [ 24 declarations: [
@@ -28,7 +31,8 @@ import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-i
28 MyAccountVideoChannelsComponent, 31 MyAccountVideoChannelsComponent,
29 MyAccountVideoChannelCreateComponent, 32 MyAccountVideoChannelCreateComponent,
30 MyAccountVideoChannelUpdateComponent, 33 MyAccountVideoChannelUpdateComponent,
31 ActorAvatarInfoComponent 34 ActorAvatarInfoComponent,
35 MyAccountVideoImportsComponent
32 ], 36 ],
33 37
34 exports: [ 38 exports: [
diff --git a/client/src/app/shared/video-import/video-import.service.ts b/client/src/app/shared/video-import/video-import.service.ts
index b4709866a..59b58ab38 100644
--- a/client/src/app/shared/video-import/video-import.service.ts
+++ b/client/src/app/shared/video-import/video-import.service.ts
@@ -1,5 +1,5 @@
1import { catchError } from 'rxjs/operators' 1import { catchError, map, switchMap } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http' 2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { Observable } from 'rxjs' 4import { Observable } from 'rxjs'
5import { VideoImport } from '../../../../../shared' 5import { VideoImport } from '../../../../../shared'
@@ -8,6 +8,12 @@ import { RestExtractor, RestService } from '../rest'
8import { VideoImportCreate } from '../../../../../shared/models/videos/video-import-create.model' 8import { VideoImportCreate } from '../../../../../shared/models/videos/video-import-create.model'
9import { objectToFormData } from '@app/shared/misc/utils' 9import { objectToFormData } from '@app/shared/misc/utils'
10import { VideoUpdate } from '../../../../../shared/models/videos' 10import { VideoUpdate } from '../../../../../shared/models/videos'
11import { ResultList } from '../../../../../shared/models/result-list.model'
12import { UserService } from '@app/shared/users/user.service'
13import { SortMeta } from 'primeng/components/common/sortmeta'
14import { RestPagination } from '@app/shared/rest'
15import { ServerService } from '@app/core'
16import { peertubeTranslate } from '@app/shared/i18n/i18n-utils'
11 17
12@Injectable() 18@Injectable()
13export class VideoImportService { 19export class VideoImportService {
@@ -16,7 +22,8 @@ export class VideoImportService {
16 constructor ( 22 constructor (
17 private authHttp: HttpClient, 23 private authHttp: HttpClient,
18 private restService: RestService, 24 private restService: RestService,
19 private restExtractor: RestExtractor 25 private restExtractor: RestExtractor,
26 private serverService: ServerService
20 ) {} 27 ) {}
21 28
22 importVideo (targetUrl: string, video: VideoUpdate): Observable<VideoImport> { 29 importVideo (targetUrl: string, video: VideoUpdate): Observable<VideoImport> {
@@ -53,4 +60,29 @@ export class VideoImportService {
53 .pipe(catchError(res => this.restExtractor.handleError(res))) 60 .pipe(catchError(res => this.restExtractor.handleError(res)))
54 } 61 }
55 62
63 getMyVideoImports (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoImport>> {
64 let params = new HttpParams()
65 params = this.restService.addRestGetParams(params, pagination, sort)
66
67 return this.authHttp
68 .get<ResultList<VideoImport>>(UserService.BASE_USERS_URL + '/me/videos/imports', { params })
69 .pipe(
70 switchMap(res => this.extractVideoImports(res)),
71 map(res => this.restExtractor.convertResultListDateToHuman(res)),
72 catchError(err => this.restExtractor.handleError(err))
73 )
74 }
75
76 private extractVideoImports (result: ResultList<VideoImport>): Observable<ResultList<VideoImport>> {
77 return this.serverService.localeObservable
78 .pipe(
79 map(translations => {
80 result.data.forEach(d =>
81 d.state.label = peertubeTranslate(d.state.label, translations)
82 )
83
84 return result
85 })
86 )
87 }
56} 88}
diff --git a/server.ts b/server.ts
index 9aaa64dbf..9094ac943 100644
--- a/server.ts
+++ b/server.ts
@@ -152,7 +152,7 @@ app.use(function (err, req, res, next) {
152 error = err.stack || err.message || err 152 error = err.stack || err.message || err
153 } 153 }
154 154
155 logger.error('Error in controller.', { error }) 155 logger.error('Error in controller.', { err: error })
156 return res.status(err.status || 500).end() 156 return res.status(err.status || 500).end()
157}) 157})
158 158
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index dbe736bff..6e5f9913e 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -29,7 +29,12 @@ import {
29 usersUpdateValidator, 29 usersUpdateValidator,
30 usersVideoRatingValidator 30 usersVideoRatingValidator
31} from '../../middlewares' 31} from '../../middlewares'
32import { usersAskResetPasswordValidator, usersResetPasswordValidator, videosSortValidator } from '../../middlewares/validators' 32import {
33 usersAskResetPasswordValidator,
34 usersResetPasswordValidator,
35 videoImportsSortValidator,
36 videosSortValidator
37} from '../../middlewares/validators'
33import { AccountVideoRateModel } from '../../models/account/account-video-rate' 38import { AccountVideoRateModel } from '../../models/account/account-video-rate'
34import { UserModel } from '../../models/account/user' 39import { UserModel } from '../../models/account/user'
35import { OAuthTokenModel } from '../../models/oauth/oauth-token' 40import { OAuthTokenModel } from '../../models/oauth/oauth-token'
@@ -40,6 +45,7 @@ import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.mo
40import { updateAvatarValidator } from '../../middlewares/validators/avatar' 45import { updateAvatarValidator } from '../../middlewares/validators/avatar'
41import { updateActorAvatarFile } from '../../lib/avatar' 46import { updateActorAvatarFile } from '../../lib/avatar'
42import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger' 47import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger'
48import { VideoImportModel } from '../../models/video/video-import'
43 49
44const auditLogger = auditLoggerFactory('users') 50const auditLogger = auditLoggerFactory('users')
45 51
@@ -62,6 +68,16 @@ usersRouter.get('/me/video-quota-used',
62 asyncMiddleware(getUserVideoQuotaUsed) 68 asyncMiddleware(getUserVideoQuotaUsed)
63) 69)
64 70
71
72usersRouter.get('/me/videos/imports',
73 authenticate,
74 paginationValidator,
75 videoImportsSortValidator,
76 setDefaultSort,
77 setDefaultPagination,
78 asyncMiddleware(getUserVideoImports)
79)
80
65usersRouter.get('/me/videos', 81usersRouter.get('/me/videos',
66 authenticate, 82 authenticate,
67 paginationValidator, 83 paginationValidator,
@@ -178,6 +194,18 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
178 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 194 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
179} 195}
180 196
197async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) {
198 const user = res.locals.oauth.token.User as UserModel
199 const resultList = await VideoImportModel.listUserVideoImportsForApi(
200 user.Account.id,
201 req.query.start as number,
202 req.query.count as number,
203 req.query.sort
204 )
205
206 return res.json(getFormattedObjects(resultList.data, resultList.total))
207}
208
181async function createUser (req: express.Request, res: express.Response) { 209async function createUser (req: express.Request, res: express.Response) {
182 const body: UserCreate = req.body 210 const body: UserCreate = req.body
183 const userToCreate = new UserModel({ 211 const userToCreate = new UserModel({
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index 74d3e213b..43156bb22 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -95,7 +95,7 @@ function titleTruncation (title: string) {
95} 95}
96 96
97function descriptionTruncation (description: string) { 97function descriptionTruncation (description: string) {
98 if (!description) return undefined 98 if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined
99 99
100 return truncate(description, { 100 return truncate(description, {
101 'length': CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max, 101 'length': CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max,
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index cc363d4f2..feb45e4d0 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -37,6 +37,7 @@ const SORTABLE_COLUMNS = {
37 VIDEO_ABUSES: [ 'id', 'createdAt' ], 37 VIDEO_ABUSES: [ 'id', 'createdAt' ],
38 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 38 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
39 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ], 39 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
40 VIDEO_IMPORTS: [ 'createdAt' ],
40 VIDEO_COMMENT_THREADS: [ 'createdAt' ], 41 VIDEO_COMMENT_THREADS: [ 'createdAt' ],
41 BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], 42 BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
42 FOLLOWERS: [ 'createdAt' ], 43 FOLLOWERS: [ 'createdAt' ],
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 2f219e986..5a7722153 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -35,7 +35,7 @@ async function processVideoImport (job: Bull.Job) {
35 35
36 // Get information about this video 36 // Get information about this video
37 const { videoFileResolution } = await getVideoFileResolution(tempVideoPath) 37 const { videoFileResolution } = await getVideoFileResolution(tempVideoPath)
38 const fps = await getVideoFileFPS(tempVideoPath) 38 const fps = await getVideoFileFPS(tempVideoPath + 's')
39 const stats = await statPromise(tempVideoPath) 39 const stats = await statPromise(tempVideoPath)
40 const duration = await getDurationFromVideoFile(tempVideoPath) 40 const duration = await getDurationFromVideoFile(tempVideoPath)
41 41
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 00bde548c..d85611773 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -8,6 +8,7 @@ const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
8const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) 8const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
9const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) 9const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
10const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) 10const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH)
11const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS)
11const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) 12const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
12const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) 13const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
13const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS) 14const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
@@ -19,6 +20,7 @@ const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
19const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) 20const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
20const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) 21const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
21const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) 22const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
23const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS)
22const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) 24const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS)
23const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) 25const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS)
24const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) 26const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
@@ -32,6 +34,7 @@ export {
32 usersSortValidator, 34 usersSortValidator,
33 videoAbusesSortValidator, 35 videoAbusesSortValidator,
34 videoChannelsSortValidator, 36 videoChannelsSortValidator,
37 videoImportsSortValidator,
35 videosSearchSortValidator, 38 videosSearchSortValidator,
36 videosSortValidator, 39 videosSortValidator,
37 blacklistSortValidator, 40 blacklistSortValidator,
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 89eeafd6a..6b8a16b65 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -1,4 +1,5 @@
1import { 1import {
2 AfterUpdate,
2 AllowNull, 3 AllowNull,
3 BelongsTo, 4 BelongsTo,
4 Column, 5 Column,
@@ -12,13 +13,14 @@ import {
12 Table, 13 Table,
13 UpdatedAt 14 UpdatedAt
14} from 'sequelize-typescript' 15} from 'sequelize-typescript'
15import { CONSTRAINTS_FIELDS } from '../../initializers' 16import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers'
16import { throwIfNotValid } from '../utils' 17import { getSort, throwIfNotValid } from '../utils'
17import { VideoModel } from './video' 18import { VideoModel } from './video'
18import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' 19import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
19import { VideoImport, VideoImportState } from '../../../shared' 20import { VideoImport, VideoImportState } from '../../../shared'
20import { VideoChannelModel } from './video-channel' 21import { VideoChannelModel } from './video-channel'
21import { AccountModel } from '../account/account' 22import { AccountModel } from '../account/account'
23import { TagModel } from './tag'
22 24
23@DefaultScope({ 25@DefaultScope({
24 include: [ 26 include: [
@@ -35,6 +37,10 @@ import { AccountModel } from '../account/account'
35 required: true 37 required: true
36 } 38 }
37 ] 39 ]
40 },
41 {
42 model: () => TagModel,
43 required: false
38 } 44 }
39 ] 45 ]
40 } 46 }
@@ -79,27 +85,89 @@ export class VideoImportModel extends Model<VideoImportModel> {
79 85
80 @BelongsTo(() => VideoModel, { 86 @BelongsTo(() => VideoModel, {
81 foreignKey: { 87 foreignKey: {
82 allowNull: false 88 allowNull: true
83 }, 89 },
84 onDelete: 'CASCADE' 90 onDelete: 'set null'
85 }) 91 })
86 Video: VideoModel 92 Video: VideoModel
87 93
94 @AfterUpdate
95 static deleteVideoIfFailed (instance: VideoImportModel, options) {
96 if (instance.state === VideoImportState.FAILED) {
97 return instance.Video.destroy({ transaction: options.transaction })
98 }
99
100 return undefined
101 }
102
88 static loadAndPopulateVideo (id: number) { 103 static loadAndPopulateVideo (id: number) {
89 return VideoImportModel.findById(id) 104 return VideoImportModel.findById(id)
90 } 105 }
91 106
107 static listUserVideoImportsForApi (accountId: number, start: number, count: number, sort: string) {
108 const query = {
109 offset: start,
110 limit: count,
111 order: getSort(sort),
112 include: [
113 {
114 model: VideoModel,
115 required: true,
116 include: [
117 {
118 model: VideoChannelModel,
119 required: true,
120 include: [
121 {
122 model: AccountModel,
123 required: true,
124 where: {
125 id: accountId
126 }
127 }
128 ]
129 },
130 {
131 model: TagModel,
132 required: false
133 }
134 ]
135 }
136 ]
137 }
138
139 return VideoImportModel.unscoped()
140 .findAndCountAll(query)
141 .then(({ rows, count }) => {
142 return {
143 data: rows,
144 total: count
145 }
146 })
147 }
148
92 toFormattedJSON (): VideoImport { 149 toFormattedJSON (): VideoImport {
93 const videoFormatOptions = { 150 const videoFormatOptions = {
94 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } 151 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
95 } 152 }
96 const video = Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { 153 const video = this.Video
97 tags: this.Video.Tags.map(t => t.name) 154 ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), {
98 }) 155 tags: this.Video.Tags.map(t => t.name)
156 })
157 : undefined
99 158
100 return { 159 return {
101 targetUrl: this.targetUrl, 160 targetUrl: this.targetUrl,
161 state: {
162 id: this.state,
163 label: VideoImportModel.getStateLabel(this.state)
164 },
165 updatedAt: this.updatedAt.toISOString(),
166 createdAt: this.createdAt.toISOString(),
102 video 167 video
103 } 168 }
104 } 169 }
170 private static getStateLabel (id: number) {
171 return VIDEO_IMPORT_STATES[id] || 'Unknown'
172 }
105} 173}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 459fcb31e..f32010014 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1569,21 +1569,25 @@ export class VideoModel extends Model<VideoModel> {
1569 removeThumbnail () { 1569 removeThumbnail () {
1570 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) 1570 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
1571 return unlinkPromise(thumbnailPath) 1571 return unlinkPromise(thumbnailPath)
1572 .catch(err => logger.warn('Cannot delete thumbnail %s.', thumbnailPath, { err }))
1572 } 1573 }
1573 1574
1574 removePreview () { 1575 removePreview () {
1575 // Same name than video thumbnail 1576 const previewPath = join(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
1576 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) 1577 return unlinkPromise(previewPath)
1578 .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err }))
1577 } 1579 }
1578 1580
1579 removeFile (videoFile: VideoFileModel) { 1581 removeFile (videoFile: VideoFileModel) {
1580 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) 1582 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
1581 return unlinkPromise(filePath) 1583 return unlinkPromise(filePath)
1584 .catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
1582 } 1585 }
1583 1586
1584 removeTorrent (videoFile: VideoFileModel) { 1587 removeTorrent (videoFile: VideoFileModel) {
1585 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 1588 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1586 return unlinkPromise(torrentPath) 1589 return unlinkPromise(torrentPath)
1590 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
1587 } 1591 }
1588 1592
1589 getActivityStreamDuration () { 1593 getActivityStreamDuration () {
diff --git a/shared/models/videos/video-import.model.ts b/shared/models/videos/video-import.model.ts
index 858108599..b23e6b245 100644
--- a/shared/models/videos/video-import.model.ts
+++ b/shared/models/videos/video-import.model.ts
@@ -1,7 +1,12 @@
1import { Video } from './video.model' 1import { Video } from './video.model'
2import { VideoConstant } from './video-constant.model'
3import { VideoImportState } from '../../index'
2 4
3export interface VideoImport { 5export interface VideoImport {
4 targetUrl: string 6 targetUrl: string
7 createdAt: string
8 updatedAt: string
9 state: VideoConstant<VideoImportState>
5 10
6 video: Video & { tags: string[] } 11 video?: Video & { tags: string[] }
7} 12}