diff options
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/account/account.ts | 1 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 7 | ||||
-rw-r--r-- | server/models/video/video-import.ts | 175 | ||||
-rw-r--r-- | server/models/video/video.ts | 19 |
4 files changed, 195 insertions, 7 deletions
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index d674d8d22..66f5dcf2e 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -16,7 +16,6 @@ import { | |||
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { Account } from '../../../shared/models/actors' | 17 | import { Account } from '../../../shared/models/actors' |
18 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' | 18 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' |
19 | import { logger } from '../../helpers/logger' | ||
20 | import { sendDeleteActor } from '../../lib/activitypub/send' | 19 | import { sendDeleteActor } from '../../lib/activitypub/send' |
21 | import { ActorModel } from '../activitypub/actor' | 20 | import { ActorModel } from '../activitypub/actor' |
22 | import { ApplicationModel } from '../application/application' | 21 | import { ApplicationModel } from '../application/application' |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 267032e2a..35d7c35e8 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -91,6 +91,9 @@ enum ScopeNames { | |||
91 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] | 91 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] |
92 | }, | 92 | }, |
93 | { | 93 | { |
94 | fields: [ 'sharedInboxUrl' ] | ||
95 | }, | ||
96 | { | ||
94 | fields: [ 'serverId' ] | 97 | fields: [ 'serverId' ] |
95 | }, | 98 | }, |
96 | { | 99 | { |
@@ -454,6 +457,10 @@ export class ActorModel extends Model<ActorModel> { | |||
454 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | 457 | return 'acct:' + this.preferredUsername + '@' + this.getHost() |
455 | } | 458 | } |
456 | 459 | ||
460 | getIdentifier () { | ||
461 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | ||
462 | } | ||
463 | |||
457 | getHost () { | 464 | getHost () { |
458 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST | 465 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST |
459 | } | 466 | } |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts new file mode 100644 index 000000000..eca87163d --- /dev/null +++ b/server/models/video/video-import.ts | |||
@@ -0,0 +1,175 @@ | |||
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 { VideoModel } from './video' | ||
19 | import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' | ||
20 | import { VideoImport, VideoImportState } from '../../../shared' | ||
21 | import { VideoChannelModel } from './video-channel' | ||
22 | import { AccountModel } from '../account/account' | ||
23 | import { TagModel } from './tag' | ||
24 | |||
25 | @DefaultScope({ | ||
26 | include: [ | ||
27 | { | ||
28 | model: () => VideoModel, | ||
29 | required: false, | ||
30 | include: [ | ||
31 | { | ||
32 | model: () => VideoChannelModel, | ||
33 | required: true, | ||
34 | include: [ | ||
35 | { | ||
36 | model: () => AccountModel, | ||
37 | required: true | ||
38 | } | ||
39 | ] | ||
40 | }, | ||
41 | { | ||
42 | model: () => TagModel | ||
43 | } | ||
44 | ] | ||
45 | } | ||
46 | ] | ||
47 | }) | ||
48 | |||
49 | @Table({ | ||
50 | tableName: 'videoImport', | ||
51 | indexes: [ | ||
52 | { | ||
53 | fields: [ 'videoId' ], | ||
54 | unique: true | ||
55 | } | ||
56 | ] | ||
57 | }) | ||
58 | export class VideoImportModel extends Model<VideoImportModel> { | ||
59 | @CreatedAt | ||
60 | createdAt: Date | ||
61 | |||
62 | @UpdatedAt | ||
63 | updatedAt: Date | ||
64 | |||
65 | @AllowNull(false) | ||
66 | @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl')) | ||
67 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) | ||
68 | targetUrl: string | ||
69 | |||
70 | @AllowNull(false) | ||
71 | @Default(null) | ||
72 | @Is('VideoImportState', value => throwIfNotValid(value, isVideoImportStateValid, 'state')) | ||
73 | @Column | ||
74 | state: VideoImportState | ||
75 | |||
76 | @AllowNull(true) | ||
77 | @Default(null) | ||
78 | @Column(DataType.TEXT) | ||
79 | error: string | ||
80 | |||
81 | @ForeignKey(() => VideoModel) | ||
82 | @Column | ||
83 | videoId: number | ||
84 | |||
85 | @BelongsTo(() => VideoModel, { | ||
86 | foreignKey: { | ||
87 | allowNull: true | ||
88 | }, | ||
89 | onDelete: 'set null' | ||
90 | }) | ||
91 | Video: VideoModel | ||
92 | |||
93 | @AfterUpdate | ||
94 | static deleteVideoIfFailed (instance: VideoImportModel, options) { | ||
95 | if (instance.state === VideoImportState.FAILED) { | ||
96 | return instance.Video.destroy({ transaction: options.transaction }) | ||
97 | } | ||
98 | |||
99 | return undefined | ||
100 | } | ||
101 | |||
102 | static loadAndPopulateVideo (id: number) { | ||
103 | return VideoImportModel.findById(id) | ||
104 | } | ||
105 | |||
106 | static listUserVideoImportsForApi (accountId: number, start: number, count: number, sort: string) { | ||
107 | const query = { | ||
108 | distinct: true, | ||
109 | offset: start, | ||
110 | limit: count, | ||
111 | order: getSort(sort), | ||
112 | include: [ | ||
113 | { | ||
114 | model: VideoModel, | ||
115 | required: false, | ||
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 | |||
149 | toFormattedJSON (): VideoImport { | ||
150 | const videoFormatOptions = { | ||
151 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } | ||
152 | } | ||
153 | const video = this.Video | ||
154 | ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { | ||
155 | tags: this.Video.Tags.map(t => t.name) | ||
156 | }) | ||
157 | : undefined | ||
158 | |||
159 | return { | ||
160 | id: this.id, | ||
161 | targetUrl: this.targetUrl, | ||
162 | state: { | ||
163 | id: this.state, | ||
164 | label: VideoImportModel.getStateLabel(this.state) | ||
165 | }, | ||
166 | error: this.error, | ||
167 | updatedAt: this.updatedAt.toISOString(), | ||
168 | createdAt: this.createdAt.toISOString(), | ||
169 | video | ||
170 | } | ||
171 | } | ||
172 | private static getStateLabel (id: number) { | ||
173 | return VIDEO_IMPORT_STATES[id] || 'Unknown' | ||
174 | } | ||
175 | } | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index a6c4620b2..39fe21007 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -377,7 +377,7 @@ type AvailableForListOptions = { | |||
377 | include: [ | 377 | include: [ |
378 | { | 378 | { |
379 | model: () => VideoFileModel.unscoped(), | 379 | model: () => VideoFileModel.unscoped(), |
380 | required: true | 380 | required: false |
381 | } | 381 | } |
382 | ] | 382 | ] |
383 | }, | 383 | }, |
@@ -957,8 +957,10 @@ export class VideoModel extends Model<VideoModel> { | |||
957 | }) | 957 | }) |
958 | } | 958 | } |
959 | 959 | ||
960 | static load (id: number) { | 960 | static load (id: number, t?: Sequelize.Transaction) { |
961 | return VideoModel.findById(id) | 961 | const options = t ? { transaction: t } : undefined |
962 | |||
963 | return VideoModel.findById(id, options) | ||
962 | } | 964 | } |
963 | 965 | ||
964 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { | 966 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { |
@@ -1353,7 +1355,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1353 | mimeType: VIDEO_EXT_MIMETYPE[file.extname], | 1355 | mimeType: VIDEO_EXT_MIMETYPE[file.extname], |
1354 | href: this.getVideoFileUrl(file, baseUrlHttp), | 1356 | href: this.getVideoFileUrl(file, baseUrlHttp), |
1355 | width: file.resolution, | 1357 | width: file.resolution, |
1356 | size: file.size | 1358 | size: file.size, |
1359 | fps: file.fps | ||
1357 | }) | 1360 | }) |
1358 | 1361 | ||
1359 | url.push({ | 1362 | url.push({ |
@@ -1569,21 +1572,25 @@ export class VideoModel extends Model<VideoModel> { | |||
1569 | removeThumbnail () { | 1572 | removeThumbnail () { |
1570 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | 1573 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) |
1571 | return unlinkPromise(thumbnailPath) | 1574 | return unlinkPromise(thumbnailPath) |
1575 | .catch(err => logger.warn('Cannot delete thumbnail %s.', thumbnailPath, { err })) | ||
1572 | } | 1576 | } |
1573 | 1577 | ||
1574 | removePreview () { | 1578 | removePreview () { |
1575 | // Same name than video thumbnail | 1579 | const previewPath = join(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) |
1576 | return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) | 1580 | return unlinkPromise(previewPath) |
1581 | .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err })) | ||
1577 | } | 1582 | } |
1578 | 1583 | ||
1579 | removeFile (videoFile: VideoFileModel) { | 1584 | removeFile (videoFile: VideoFileModel) { |
1580 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | 1585 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) |
1581 | return unlinkPromise(filePath) | 1586 | return unlinkPromise(filePath) |
1587 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) | ||
1582 | } | 1588 | } |
1583 | 1589 | ||
1584 | removeTorrent (videoFile: VideoFileModel) { | 1590 | removeTorrent (videoFile: VideoFileModel) { |
1585 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1591 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1586 | return unlinkPromise(torrentPath) | 1592 | return unlinkPromise(torrentPath) |
1593 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | ||
1587 | } | 1594 | } |
1588 | 1595 | ||
1589 | getActivityStreamDuration () { | 1596 | getActivityStreamDuration () { |