1 import { WhereOptions } from 'sequelize'
16 } from 'sequelize-typescript'
17 import { afterCommitIfTransaction } from '@server/helpers/database-utils'
18 import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import'
19 import { VideoImport, VideoImportState } from '@shared/models'
20 import { AttributesOnly } from '@shared/typescript-utils'
21 import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
22 import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
23 import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
24 import { UserModel } from '../user/user'
25 import { getSort, throwIfNotValid } from '../utils'
26 import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
28 @DefaultScope(() => ({
31 model: UserModel.unscoped(),
35 model: VideoModel.scope([
36 VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
37 VideoModelScopeNames.WITH_TAGS,
38 VideoModelScopeNames.WITH_THUMBNAILS
46 tableName: 'videoImport',
49 fields: [ 'videoId' ],
57 export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> {
66 @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl', true))
67 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max))
72 @Is('VideoImportMagnetUri', value => throwIfNotValid(value, isVideoMagnetUriValid, 'magnetUri', true))
73 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) // Use the same constraints than URLs
78 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_NAME.max))
83 @Is('VideoImportState', value => throwIfNotValid(value, isVideoImportStateValid, 'state'))
85 state: VideoImportState
89 @Column(DataType.TEXT)
92 @ForeignKey(() => UserModel)
96 @BelongsTo(() => UserModel, {
104 @ForeignKey(() => VideoModel)
108 @BelongsTo(() => VideoModel, {
117 static deleteVideoIfFailed (instance: VideoImportModel, options) {
118 if (instance.state === VideoImportState.FAILED) {
119 return afterCommitIfTransaction(options.transaction, () => instance.Video.destroy())
125 static loadAndPopulateVideo (id: number): Promise<MVideoImportDefault> {
126 return VideoImportModel.findByPk(id)
129 static listUserVideoImportsForApi (options: {
137 const { userId, start, count, sort, targetUrl } = options
139 const where: WhereOptions = { userId }
141 if (targetUrl) where['targetUrl'] = targetUrl
147 attributes: [ 'id' ],
148 model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
154 order: getSort(sort),
159 VideoImportModel.unscoped().count(query),
160 VideoImportModel.findAll<MVideoImportDefault>(query)
161 ]).then(([ total, data ]) => ({ total, data }))
164 getTargetIdentifier () {
165 return this.targetUrl || this.magnetUri || this.torrentName
168 toFormattedJSON (this: MVideoImportFormattable): VideoImport {
169 const videoFormatOptions = {
170 completeDescription: true,
171 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
173 const video = this.Video
174 ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) })
180 targetUrl: this.targetUrl,
181 magnetUri: this.magnetUri,
182 torrentName: this.torrentName,
186 label: VideoImportModel.getStateLabel(this.state)
189 updatedAt: this.updatedAt.toISOString(),
190 createdAt: this.createdAt.toISOString(),
195 private static getStateLabel (id: number) {
196 return VIDEO_IMPORT_STATES[id] || 'Unknown'