]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/custom-validators/videos.ts
Optimize SQL requests of videos AP endpoints
[github/Chocobozzz/PeerTube.git] / server / helpers / custom-validators / videos.ts
1 import { Response } from 'express'
2 import 'express-validator'
3 import { values } from 'lodash'
4 import 'multer'
5 import * as validator from 'validator'
6 import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared'
7 import {
8 CONSTRAINTS_FIELDS,
9 VIDEO_CATEGORIES,
10 VIDEO_LICENCES,
11 VIDEO_MIMETYPE_EXT,
12 VIDEO_PRIVACIES,
13 VIDEO_RATE_TYPES,
14 VIDEO_STATES
15 } from '../../initializers'
16 import { VideoModel } from '../../models/video/video'
17 import { exists, isArray, isFileValid } from './misc'
18 import { VideoChannelModel } from '../../models/video/video-channel'
19 import { UserModel } from '../../models/account/user'
20 import * as magnetUtil from 'magnet-uri'
21
22 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
23
24 function isVideoCategoryValid (value: any) {
25 return value === null || VIDEO_CATEGORIES[ value ] !== undefined
26 }
27
28 function isVideoStateValid (value: any) {
29 return exists(value) && VIDEO_STATES[ value ] !== undefined
30 }
31
32 function isVideoLicenceValid (value: any) {
33 return value === null || VIDEO_LICENCES[ value ] !== undefined
34 }
35
36 function isVideoLanguageValid (value: any) {
37 return value === null ||
38 (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
39 }
40
41 function isVideoDurationValid (value: string) {
42 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
43 }
44
45 function isVideoTruncatedDescriptionValid (value: string) {
46 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
47 }
48
49 function isVideoDescriptionValid (value: string) {
50 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
51 }
52
53 function isVideoSupportValid (value: string) {
54 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
55 }
56
57 function isVideoNameValid (value: string) {
58 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
59 }
60
61 function isVideoTagValid (tag: string) {
62 return exists(tag) && validator.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
63 }
64
65 function isVideoTagsValid (tags: string[]) {
66 return tags === null || (
67 isArray(tags) &&
68 validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
69 tags.every(tag => isVideoTagValid(tag))
70 )
71 }
72
73 function isVideoViewsValid (value: string) {
74 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
75 }
76
77 function isVideoRatingTypeValid (value: string) {
78 return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
79 }
80
81 const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
82 const videoFileTypesRegex = videoFileTypes.join('|')
83
84 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
85 return isFileValid(files, videoFileTypesRegex, 'videofile', null)
86 }
87
88 const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
89 .map(v => v.replace('.', ''))
90 .join('|')
91 const videoImageTypesRegex = `image/(${videoImageTypes})`
92
93 function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
94 return isFileValid(files, videoImageTypesRegex, field, CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max, true)
95 }
96
97 function isVideoPrivacyValid (value: number) {
98 return validator.isInt(value + '') && VIDEO_PRIVACIES[ value ] !== undefined
99 }
100
101 function isScheduleVideoUpdatePrivacyValid (value: number) {
102 return validator.isInt(value + '') &&
103 (
104 value === VideoPrivacy.UNLISTED ||
105 value === VideoPrivacy.PUBLIC
106 )
107 }
108
109 function isVideoFileInfoHashValid (value: string | null | undefined) {
110 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
111 }
112
113 function isVideoFileResolutionValid (value: string) {
114 return exists(value) && validator.isInt(value + '')
115 }
116
117 function isVideoFPSResolutionValid (value: string) {
118 return value === null || validator.isInt(value + '')
119 }
120
121 function isVideoFileSizeValid (value: string) {
122 return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
123 }
124
125 function isVideoMagnetUriValid (value: string) {
126 if (!exists(value)) return false
127
128 const parsed = magnetUtil.decode(value)
129 return parsed && isVideoFileInfoHashValid(parsed.infoHash)
130 }
131
132 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
133 // Retrieve the user who did the request
134 if (video.isOwned() === false) {
135 res.status(403)
136 .json({ error: 'Cannot manage a video of another server.' })
137 .end()
138 return false
139 }
140
141 // Check if the user can delete the video
142 // The user can delete it if he has the right
143 // Or if s/he is the video's account
144 const account = video.VideoChannel.Account
145 if (user.hasRight(right) === false && account.userId !== user.id) {
146 res.status(403)
147 .json({ error: 'Cannot manage a video of another user.' })
148 .end()
149 return false
150 }
151
152 return true
153 }
154
155 export type VideoFetchType = 'all' | 'only-video' | 'id' | 'none'
156 async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') {
157 let video: VideoModel | null
158
159 if (fetchType === 'all') {
160 video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id)
161 } else if (fetchType === 'only-video') {
162 video = await VideoModel.load(id)
163 } else if (fetchType === 'id' || fetchType === 'none') {
164 video = await VideoModel.loadOnlyId(id)
165 }
166
167 if (video === null) {
168 res.status(404)
169 .json({ error: 'Video not found' })
170 .end()
171
172 return false
173 }
174
175 if (fetchType !== 'none') res.locals.video = video
176 return true
177 }
178
179 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
180 if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
181 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
182 if (videoChannel === null) {
183 res.status(400)
184 .json({ error: 'Unknown video `video channel` on this instance.' })
185 .end()
186
187 return false
188 }
189
190 res.locals.videoChannel = videoChannel
191 return true
192 }
193
194 const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
195 if (videoChannel === null) {
196 res.status(400)
197 .json({ error: 'Unknown video `video channel` for this account.' })
198 .end()
199
200 return false
201 }
202
203 res.locals.videoChannel = videoChannel
204 return true
205 }
206
207 // ---------------------------------------------------------------------------
208
209 export {
210 isVideoCategoryValid,
211 checkUserCanManageVideo,
212 isVideoLicenceValid,
213 isVideoLanguageValid,
214 isVideoTruncatedDescriptionValid,
215 isVideoDescriptionValid,
216 isVideoFileInfoHashValid,
217 isVideoNameValid,
218 isVideoTagsValid,
219 isVideoFPSResolutionValid,
220 isScheduleVideoUpdatePrivacyValid,
221 isVideoFile,
222 isVideoMagnetUriValid,
223 isVideoStateValid,
224 isVideoViewsValid,
225 isVideoRatingTypeValid,
226 isVideoDurationValid,
227 isVideoTagValid,
228 isVideoPrivacyValid,
229 isVideoFileResolutionValid,
230 isVideoFileSizeValid,
231 isVideoExist,
232 isVideoImage,
233 isVideoChannelOfAccountExist,
234 isVideoSupportValid
235 }