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