]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/custom-validators/videos.ts
Optimize SQL requests of watch page API 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 async function isVideoExist (id: string, res: Response, fetchType: 'all' | 'only-video' | 'id' | 'none' = 'all') {
156 let video: VideoModel | null
157
158 if (fetchType === 'all') {
159 video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id)
160 } else if (fetchType === 'only-video') {
161 video = await VideoModel.load(id)
162 } else if (fetchType === 'id' || fetchType === 'none') {
163 video = await VideoModel.loadOnlyId(id)
164 }
165
166 if (video === null) {
167 res.status(404)
168 .json({ error: 'Video not found' })
169 .end()
170
171 return false
172 }
173
174 if (fetchType !== 'none') res.locals.video = video
175 return true
176 }
177
178 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
179 if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
180 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
181 if (videoChannel === null) {
182 res.status(400)
183 .json({ error: 'Unknown video `video channel` on this instance.' })
184 .end()
185
186 return false
187 }
188
189 res.locals.videoChannel = videoChannel
190 return true
191 }
192
193 const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
194 if (videoChannel === null) {
195 res.status(400)
196 .json({ error: 'Unknown video `video channel` for this account.' })
197 .end()
198
199 return false
200 }
201
202 res.locals.videoChannel = videoChannel
203 return true
204 }
205
206 // ---------------------------------------------------------------------------
207
208 export {
209 isVideoCategoryValid,
210 checkUserCanManageVideo,
211 isVideoLicenceValid,
212 isVideoLanguageValid,
213 isVideoTruncatedDescriptionValid,
214 isVideoDescriptionValid,
215 isVideoFileInfoHashValid,
216 isVideoNameValid,
217 isVideoTagsValid,
218 isVideoFPSResolutionValid,
219 isScheduleVideoUpdatePrivacyValid,
220 isVideoFile,
221 isVideoMagnetUriValid,
222 isVideoStateValid,
223 isVideoViewsValid,
224 isVideoRatingTypeValid,
225 isVideoDurationValid,
226 isVideoTagValid,
227 isVideoPrivacyValid,
228 isVideoFileResolutionValid,
229 isVideoFileSizeValid,
230 isVideoExist,
231 isVideoImage,
232 isVideoChannelOfAccountExist,
233 isVideoSupportValid
234 }