]>
Commit | Line | Data |
---|---|---|
1 | import { WhereOptions } from 'sequelize' | |
2 | import { | |
3 | AfterUpdate, | |
4 | AllowNull, | |
5 | BelongsTo, | |
6 | Column, | |
7 | CreatedAt, | |
8 | DataType, | |
9 | Default, | |
10 | DefaultScope, | |
11 | ForeignKey, | |
12 | Is, | |
13 | Model, | |
14 | Table, | |
15 | UpdatedAt | |
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' | |
27 | ||
28 | @DefaultScope(() => ({ | |
29 | include: [ | |
30 | { | |
31 | model: UserModel.unscoped(), | |
32 | required: true | |
33 | }, | |
34 | { | |
35 | model: VideoModel.scope([ | |
36 | VideoModelScopeNames.WITH_ACCOUNT_DETAILS, | |
37 | VideoModelScopeNames.WITH_TAGS, | |
38 | VideoModelScopeNames.WITH_THUMBNAILS | |
39 | ]), | |
40 | required: false | |
41 | } | |
42 | ] | |
43 | })) | |
44 | ||
45 | @Table({ | |
46 | tableName: 'videoImport', | |
47 | indexes: [ | |
48 | { | |
49 | fields: [ 'videoId' ], | |
50 | unique: true | |
51 | }, | |
52 | { | |
53 | fields: [ 'userId' ] | |
54 | } | |
55 | ] | |
56 | }) | |
57 | export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> { | |
58 | @CreatedAt | |
59 | createdAt: Date | |
60 | ||
61 | @UpdatedAt | |
62 | updatedAt: Date | |
63 | ||
64 | @AllowNull(true) | |
65 | @Default(null) | |
66 | @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl', true)) | |
67 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) | |
68 | targetUrl: string | |
69 | ||
70 | @AllowNull(true) | |
71 | @Default(null) | |
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 | |
74 | magnetUri: string | |
75 | ||
76 | @AllowNull(true) | |
77 | @Default(null) | |
78 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_NAME.max)) | |
79 | torrentName: string | |
80 | ||
81 | @AllowNull(false) | |
82 | @Default(null) | |
83 | @Is('VideoImportState', value => throwIfNotValid(value, isVideoImportStateValid, 'state')) | |
84 | @Column | |
85 | state: VideoImportState | |
86 | ||
87 | @AllowNull(true) | |
88 | @Default(null) | |
89 | @Column(DataType.TEXT) | |
90 | error: string | |
91 | ||
92 | @ForeignKey(() => UserModel) | |
93 | @Column | |
94 | userId: number | |
95 | ||
96 | @BelongsTo(() => UserModel, { | |
97 | foreignKey: { | |
98 | allowNull: false | |
99 | }, | |
100 | onDelete: 'cascade' | |
101 | }) | |
102 | User: UserModel | |
103 | ||
104 | @ForeignKey(() => VideoModel) | |
105 | @Column | |
106 | videoId: number | |
107 | ||
108 | @BelongsTo(() => VideoModel, { | |
109 | foreignKey: { | |
110 | allowNull: true | |
111 | }, | |
112 | onDelete: 'set null' | |
113 | }) | |
114 | Video: VideoModel | |
115 | ||
116 | @AfterUpdate | |
117 | static deleteVideoIfFailed (instance: VideoImportModel, options) { | |
118 | if (instance.state === VideoImportState.FAILED) { | |
119 | return afterCommitIfTransaction(options.transaction, () => instance.Video.destroy()) | |
120 | } | |
121 | ||
122 | return undefined | |
123 | } | |
124 | ||
125 | static loadAndPopulateVideo (id: number): Promise<MVideoImportDefault> { | |
126 | return VideoImportModel.findByPk(id) | |
127 | } | |
128 | ||
129 | static listUserVideoImportsForApi (options: { | |
130 | userId: number | |
131 | start: number | |
132 | count: number | |
133 | sort: string | |
134 | ||
135 | targetUrl?: string | |
136 | }) { | |
137 | const { userId, start, count, sort, targetUrl } = options | |
138 | ||
139 | const where: WhereOptions = { userId } | |
140 | ||
141 | if (targetUrl) where['targetUrl'] = targetUrl | |
142 | ||
143 | const query = { | |
144 | distinct: true, | |
145 | include: [ | |
146 | { | |
147 | attributes: [ 'id' ], | |
148 | model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query | |
149 | required: true | |
150 | } | |
151 | ], | |
152 | offset: start, | |
153 | limit: count, | |
154 | order: getSort(sort), | |
155 | where | |
156 | } | |
157 | ||
158 | return Promise.all([ | |
159 | VideoImportModel.unscoped().count(query), | |
160 | VideoImportModel.findAll<MVideoImportDefault>(query) | |
161 | ]).then(([ total, data ]) => ({ total, data })) | |
162 | } | |
163 | ||
164 | getTargetIdentifier () { | |
165 | return this.targetUrl || this.magnetUri || this.torrentName | |
166 | } | |
167 | ||
168 | toFormattedJSON (this: MVideoImportFormattable): VideoImport { | |
169 | const videoFormatOptions = { | |
170 | completeDescription: true, | |
171 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } | |
172 | } | |
173 | const video = this.Video | |
174 | ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) }) | |
175 | : undefined | |
176 | ||
177 | return { | |
178 | id: this.id, | |
179 | ||
180 | targetUrl: this.targetUrl, | |
181 | magnetUri: this.magnetUri, | |
182 | torrentName: this.torrentName, | |
183 | ||
184 | state: { | |
185 | id: this.state, | |
186 | label: VideoImportModel.getStateLabel(this.state) | |
187 | }, | |
188 | error: this.error, | |
189 | updatedAt: this.updatedAt.toISOString(), | |
190 | createdAt: this.createdAt.toISOString(), | |
191 | video | |
192 | } | |
193 | } | |
194 | ||
195 | private static getStateLabel (id: number) { | |
196 | return VIDEO_IMPORT_STATES[id] || 'Unknown' | |
197 | } | |
198 | } |