Merge branch 'develop' into cli-wrapper
[oweals/peertube.git] / server / models / video / video-import.ts
1 import {
2   AfterUpdate,
3   AllowNull,
4   BelongsTo,
5   Column,
6   CreatedAt,
7   DataType,
8   Default,
9   DefaultScope,
10   ForeignKey,
11   Is,
12   Model,
13   Table,
14   UpdatedAt
15 } from 'sequelize-typescript'
16 import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers'
17 import { getSort, throwIfNotValid } from '../utils'
18 import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
19 import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
20 import { VideoImport, VideoImportState } from '../../../shared'
21 import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22 import { UserModel } from '../account/user'
23
24 @DefaultScope({
25   include: [
26     {
27       model: () => UserModel.unscoped(),
28       required: true
29     },
30     {
31       model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
32       required: false
33     }
34   ]
35 })
36
37 @Table({
38   tableName: 'videoImport',
39   indexes: [
40     {
41       fields: [ 'videoId' ],
42       unique: true
43     },
44     {
45       fields: [ 'userId' ]
46     }
47   ]
48 })
49 export class VideoImportModel extends Model<VideoImportModel> {
50   @CreatedAt
51   createdAt: Date
52
53   @UpdatedAt
54   updatedAt: Date
55
56   @AllowNull(true)
57   @Default(null)
58   @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl'))
59   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max))
60   targetUrl: string
61
62   @AllowNull(true)
63   @Default(null)
64   @Is('VideoImportMagnetUri', value => throwIfNotValid(value, isVideoMagnetUriValid, 'magnetUri'))
65   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) // Use the same constraints than URLs
66   magnetUri: string
67
68   @AllowNull(true)
69   @Default(null)
70   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_NAME.max))
71   torrentName: string
72
73   @AllowNull(false)
74   @Default(null)
75   @Is('VideoImportState', value => throwIfNotValid(value, isVideoImportStateValid, 'state'))
76   @Column
77   state: VideoImportState
78
79   @AllowNull(true)
80   @Default(null)
81   @Column(DataType.TEXT)
82   error: string
83
84   @ForeignKey(() => UserModel)
85   @Column
86   userId: number
87
88   @BelongsTo(() => UserModel, {
89     foreignKey: {
90       allowNull: false
91     },
92     onDelete: 'cascade'
93   })
94   User: UserModel
95
96   @ForeignKey(() => VideoModel)
97   @Column
98   videoId: number
99
100   @BelongsTo(() => VideoModel, {
101     foreignKey: {
102       allowNull: true
103     },
104     onDelete: 'set null'
105   })
106   Video: VideoModel
107
108   @AfterUpdate
109   static deleteVideoIfFailed (instance: VideoImportModel, options) {
110     if (instance.state === VideoImportState.FAILED) {
111       return instance.Video.destroy({ transaction: options.transaction })
112     }
113
114     return undefined
115   }
116
117   static loadAndPopulateVideo (id: number) {
118     return VideoImportModel.findById(id)
119   }
120
121   static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) {
122     const query = {
123       distinct: true,
124       include: [
125         {
126           model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
127           required: true
128         }
129       ],
130       offset: start,
131       limit: count,
132       order: getSort(sort),
133       where: {
134         userId
135       }
136     }
137
138     return VideoImportModel.findAndCountAll(query)
139                            .then(({ rows, count }) => {
140                              return {
141                                data: rows,
142                                total: count
143                              }
144                            })
145   }
146
147   toFormattedJSON (): VideoImport {
148     const videoFormatOptions = {
149       additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
150     }
151     const video = this.Video
152       ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), {
153         tags: this.Video.Tags.map(t => t.name)
154       })
155       : undefined
156
157     return {
158       id: this.id,
159
160       targetUrl: this.targetUrl,
161       magnetUri: this.magnetUri,
162       torrentName: this.torrentName,
163
164       state: {
165         id: this.state,
166         label: VideoImportModel.getStateLabel(this.state)
167       },
168       error: this.error,
169       updatedAt: this.updatedAt.toISOString(),
170       createdAt: this.createdAt.toISOString(),
171       video
172     }
173   }
174
175   private static getStateLabel (id: number) {
176     return VIDEO_IMPORT_STATES[id] || 'Unknown'
177   }
178 }