Upgrade sequelize
[oweals/peertube.git] / server / models / video / video-caption.ts
1 import { OrderItem, Transaction } from 'sequelize'
2 import {
3   AllowNull,
4   BeforeDestroy,
5   BelongsTo,
6   Column,
7   CreatedAt,
8   ForeignKey,
9   Is,
10   Model,
11   Scopes,
12   Table,
13   UpdatedAt
14 } from 'sequelize-typescript'
15 import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
16 import { VideoModel } from './video'
17 import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18 import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
19 import { STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants'
20 import { join } from 'path'
21 import { logger } from '../../helpers/logger'
22 import { remove } from 'fs-extra'
23 import { CONFIG } from '../../initializers/config'
24
25 export enum ScopeNames {
26   WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
27 }
28
29 @Scopes(() => ({
30   [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
31     include: [
32       {
33         attributes: [ 'uuid', 'remote' ],
34         model: VideoModel.unscoped(),
35         required: true
36       }
37     ]
38   }
39 }))
40
41 @Table({
42   tableName: 'videoCaption',
43   indexes: [
44     {
45       fields: [ 'videoId' ]
46     },
47     {
48       fields: [ 'videoId', 'language' ],
49       unique: true
50     }
51   ]
52 })
53 export class VideoCaptionModel extends Model<VideoCaptionModel> {
54   @CreatedAt
55   createdAt: Date
56
57   @UpdatedAt
58   updatedAt: Date
59
60   @AllowNull(false)
61   @Is('VideoCaptionLanguage', value => throwIfNotValid(value, isVideoCaptionLanguageValid, 'language'))
62   @Column
63   language: string
64
65   @ForeignKey(() => VideoModel)
66   @Column
67   videoId: number
68
69   @BelongsTo(() => VideoModel, {
70     foreignKey: {
71       allowNull: false
72     },
73     onDelete: 'CASCADE'
74   })
75   Video: VideoModel
76
77   @BeforeDestroy
78   static async removeFiles (instance: VideoCaptionModel) {
79     if (!instance.Video) {
80       instance.Video = await instance.$get('Video') as VideoModel
81     }
82
83     if (instance.isOwned()) {
84       logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language)
85
86       try {
87         await instance.removeCaptionFile()
88       } catch (err) {
89         logger.error('Cannot remove caption file of video %s.', instance.Video.uuid)
90       }
91     }
92
93     return undefined
94   }
95
96   static loadByVideoIdAndLanguage (videoId: string | number, language: string) {
97     const videoInclude = {
98       model: VideoModel.unscoped(),
99       attributes: [ 'id', 'remote', 'uuid' ],
100       where: buildWhereIdOrUUID(videoId)
101     }
102
103     const query = {
104       where: {
105         language
106       },
107       include: [
108         videoInclude
109       ]
110     }
111
112     return VideoCaptionModel.findOne(query)
113   }
114
115   static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) {
116     const values = {
117       videoId,
118       language
119     }
120
121     return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings
122       .then(([ caption ]) => caption)
123   }
124
125   static listVideoCaptions (videoId: number) {
126     const query = {
127       order: [ [ 'language', 'ASC' ] ] as OrderItem[],
128       where: {
129         videoId
130       }
131     }
132
133     return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query)
134   }
135
136   static getLanguageLabel (language: string) {
137     return VIDEO_LANGUAGES[language] || 'Unknown'
138   }
139
140   static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Transaction) {
141     const query = {
142       where: {
143         videoId
144       },
145       transaction
146     }
147
148     return VideoCaptionModel.destroy(query)
149   }
150
151   isOwned () {
152     return this.Video.remote === false
153   }
154
155   toFormattedJSON (): VideoCaption {
156     return {
157       language: {
158         id: this.language,
159         label: VideoCaptionModel.getLanguageLabel(this.language)
160       },
161       captionPath: this.getCaptionStaticPath()
162     }
163   }
164
165   getCaptionStaticPath () {
166     return join(STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
167   }
168
169   getCaptionName () {
170     return `${this.Video.uuid}-${this.language}.vtt`
171   }
172
173   removeCaptionFile () {
174     return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
175   }
176 }