Split types and typings
[oweals/peertube.git] / server / models / video / thumbnail.ts
1 import { join } from 'path'
2 import {
3   AfterDestroy,
4   AllowNull,
5   BelongsTo,
6   Column,
7   CreatedAt,
8   DataType,
9   Default,
10   ForeignKey,
11   Model,
12   Table,
13   UpdatedAt
14 } from 'sequelize-typescript'
15 import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants'
16 import { logger } from '../../helpers/logger'
17 import { remove } from 'fs-extra'
18 import { CONFIG } from '../../initializers/config'
19 import { VideoModel } from './video'
20 import { VideoPlaylistModel } from './video-playlist'
21 import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
22 import { MVideoAccountLight } from '@server/types/models'
23 import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
24
25 @Table({
26   tableName: 'thumbnail',
27   indexes: [
28     {
29       fields: [ 'videoId' ]
30     },
31     {
32       fields: [ 'videoPlaylistId' ],
33       unique: true
34     }
35   ]
36 })
37 export class ThumbnailModel extends Model<ThumbnailModel> {
38
39   @AllowNull(false)
40   @Column
41   filename: string
42
43   @AllowNull(true)
44   @Default(null)
45   @Column
46   height: number
47
48   @AllowNull(true)
49   @Default(null)
50   @Column
51   width: number
52
53   @AllowNull(false)
54   @Column
55   type: ThumbnailType
56
57   @AllowNull(true)
58   @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
59   fileUrl: string
60
61   @AllowNull(true)
62   @Column
63   automaticallyGenerated: boolean
64
65   @ForeignKey(() => VideoModel)
66   @Column
67   videoId: number
68
69   @BelongsTo(() => VideoModel, {
70     foreignKey: {
71       allowNull: true
72     },
73     onDelete: 'CASCADE'
74   })
75   Video: VideoModel
76
77   @ForeignKey(() => VideoPlaylistModel)
78   @Column
79   videoPlaylistId: number
80
81   @BelongsTo(() => VideoPlaylistModel, {
82     foreignKey: {
83       allowNull: true
84     },
85     onDelete: 'CASCADE'
86   })
87   VideoPlaylist: VideoPlaylistModel
88
89   @CreatedAt
90   createdAt: Date
91
92   @UpdatedAt
93   updatedAt: Date
94
95   private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
96     [ThumbnailType.MINIATURE]: {
97       label: 'miniature',
98       directory: CONFIG.STORAGE.THUMBNAILS_DIR,
99       staticPath: STATIC_PATHS.THUMBNAILS
100     },
101     [ThumbnailType.PREVIEW]: {
102       label: 'preview',
103       directory: CONFIG.STORAGE.PREVIEWS_DIR,
104       staticPath: LAZY_STATIC_PATHS.PREVIEWS
105     }
106   }
107
108   @AfterDestroy
109   static removeFiles (instance: ThumbnailModel) {
110     logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
111
112     // Don't block the transaction
113     instance.removeThumbnail()
114             .catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err))
115   }
116
117   static loadByName (filename: string) {
118     const query = {
119       where: {
120         filename
121       }
122     }
123
124     return ThumbnailModel.findOne(query)
125   }
126
127   static generateDefaultPreviewName (videoUUID: string) {
128     return videoUUID + '.jpg'
129   }
130
131   getFileUrl (video: MVideoAccountLight) {
132     const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
133
134     if (video.isOwned()) return WEBSERVER.URL + staticPath
135     if (this.fileUrl) return this.fileUrl
136
137     // Fallback if we don't have a file URL
138     return buildRemoteVideoBaseUrl(video, staticPath)
139   }
140
141   getPath () {
142     const directory = ThumbnailModel.types[this.type].directory
143     return join(directory, this.filename)
144   }
145
146   removeThumbnail () {
147     return remove(this.getPath())
148   }
149 }