diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/users.ts | 19 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 89 | ||||
-rw-r--r-- | server/helpers/custom-validators/misc.ts | 27 | ||||
-rw-r--r-- | server/helpers/custom-validators/users.ts | 26 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 29 | ||||
-rw-r--r-- | server/helpers/image-utils.ts | 21 | ||||
-rw-r--r-- | server/helpers/utils.ts | 18 | ||||
-rw-r--r-- | server/initializers/constants.ts | 10 | ||||
-rw-r--r-- | server/lib/activitypub/actor.ts | 6 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 51 | ||||
-rw-r--r-- | server/tests/api/check-params/users.ts | 8 | ||||
-rw-r--r-- | server/tests/api/check-params/videos.ts | 150 | ||||
-rw-r--r-- | server/tests/api/fixtures/preview.jpg | bin | 0 -> 4215 bytes | |||
-rw-r--r-- | server/tests/api/fixtures/thumbnail.jpg | bin | 0 -> 1457 bytes | |||
-rw-r--r-- | server/tests/api/videos/multiple-servers.ts | 16 | ||||
-rw-r--r-- | server/tests/utils/miscs/miscs.ts | 15 | ||||
-rw-r--r-- | server/tests/utils/requests/requests.ts | 19 | ||||
-rw-r--r-- | server/tests/utils/users/users.ts | 4 | ||||
-rw-r--r-- | server/tests/utils/videos/videos.ts | 83 | ||||
-rw-r--r-- | server/tools/import-youtube.ts | 2 |
20 files changed, 452 insertions, 141 deletions
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index 6e5d09695..e3067584e 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'multer' | ||
2 | import { extname, join } from 'path' | 3 | import { extname, join } from 'path' |
3 | import * as sharp from 'sharp' | ||
4 | import * as uuidv4 from 'uuid/v4' | 4 | import * as uuidv4 from 'uuid/v4' |
5 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' | 5 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' |
6 | import { unlinkPromise } from '../../helpers/core-utils' | ||
7 | import { retryTransactionWrapper } from '../../helpers/database-utils' | 6 | import { retryTransactionWrapper } from '../../helpers/database-utils' |
7 | import { processImage } from '../../helpers/image-utils' | ||
8 | import { logger } from '../../helpers/logger' | 8 | import { logger } from '../../helpers/logger' |
9 | import { createReqFiles, getFormattedObjects } from '../../helpers/utils' | 9 | import { createReqFiles, getFormattedObjects } from '../../helpers/utils' |
10 | import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers' | 10 | import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' |
11 | import { updateActorAvatarInstance } from '../../lib/activitypub' | 11 | import { updateActorAvatarInstance } from '../../lib/activitypub' |
12 | import { sendUpdateUser } from '../../lib/activitypub/send' | 12 | import { sendUpdateUser } from '../../lib/activitypub/send' |
13 | import { Emailer } from '../../lib/emailer' | 13 | import { Emailer } from '../../lib/emailer' |
@@ -42,7 +42,7 @@ import { UserModel } from '../../models/account/user' | |||
42 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' | 42 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' |
43 | import { VideoModel } from '../../models/video/video' | 43 | import { VideoModel } from '../../models/video/video' |
44 | 44 | ||
45 | const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT) | 45 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) |
46 | 46 | ||
47 | const usersRouter = express.Router() | 47 | const usersRouter = express.Router() |
48 | 48 | ||
@@ -288,17 +288,10 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next | |||
288 | const user = res.locals.oauth.token.user | 288 | const user = res.locals.oauth.token.user |
289 | const actor = user.Account.Actor | 289 | const actor = user.Account.Actor |
290 | 290 | ||
291 | const avatarDir = CONFIG.STORAGE.AVATARS_DIR | ||
292 | const source = join(avatarDir, avatarPhysicalFile.filename) | ||
293 | const extension = extname(avatarPhysicalFile.filename) | 291 | const extension = extname(avatarPhysicalFile.filename) |
294 | const avatarName = uuidv4() + extension | 292 | const avatarName = uuidv4() + extension |
295 | const destination = join(avatarDir, avatarName) | 293 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
296 | 294 | await processImage(avatarPhysicalFile, destination, AVATARS_SIZE) | |
297 | await sharp(source) | ||
298 | .resize(AVATARS_SIZE.width, AVATARS_SIZE.height) | ||
299 | .toFile(destination) | ||
300 | |||
301 | await unlinkPromise(source) | ||
302 | 295 | ||
303 | const avatar = await sequelizeTypescript.transaction(async t => { | 296 | const avatar = await sequelizeTypescript.transaction(async t => { |
304 | const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) | 297 | const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 459795141..1a4de081f 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -4,18 +4,36 @@ import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared' | |||
4 | import { renamePromise } from '../../../helpers/core-utils' | 4 | import { renamePromise } from '../../../helpers/core-utils' |
5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
6 | import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils' | 6 | import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils' |
7 | import { processImage } from '../../../helpers/image-utils' | ||
7 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
8 | import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' | 9 | import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' |
9 | import { | 10 | import { |
10 | CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, | 11 | CONFIG, |
12 | IMAGE_MIMETYPE_EXT, | ||
13 | PREVIEWS_SIZE, | ||
14 | sequelizeTypescript, | ||
15 | THUMBNAILS_SIZE, | ||
16 | VIDEO_CATEGORIES, | ||
17 | VIDEO_LANGUAGES, | ||
18 | VIDEO_LICENCES, | ||
19 | VIDEO_MIMETYPE_EXT, | ||
11 | VIDEO_PRIVACIES | 20 | VIDEO_PRIVACIES |
12 | } from '../../../initializers' | 21 | } from '../../../initializers' |
13 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' | 22 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' |
14 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' | 23 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' |
15 | import { JobQueue } from '../../../lib/job-queue' | 24 | import { JobQueue } from '../../../lib/job-queue' |
16 | import { | 25 | import { |
17 | asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator, | 26 | asyncMiddleware, |
18 | videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator | 27 | authenticate, |
28 | paginationValidator, | ||
29 | setDefaultPagination, | ||
30 | setDefaultSort, | ||
31 | videosAddValidator, | ||
32 | videosGetValidator, | ||
33 | videosRemoveValidator, | ||
34 | videosSearchValidator, | ||
35 | videosSortValidator, | ||
36 | videosUpdateValidator | ||
19 | } from '../../../middlewares' | 37 | } from '../../../middlewares' |
20 | import { TagModel } from '../../../models/video/tag' | 38 | import { TagModel } from '../../../models/video/tag' |
21 | import { VideoModel } from '../../../models/video/video' | 39 | import { VideoModel } from '../../../models/video/video' |
@@ -28,7 +46,23 @@ import { rateVideoRouter } from './rate' | |||
28 | 46 | ||
29 | const videosRouter = express.Router() | 47 | const videosRouter = express.Router() |
30 | 48 | ||
31 | const reqVideoFile = createReqFiles('videofile', CONFIG.STORAGE.VIDEOS_DIR, VIDEO_MIMETYPE_EXT) | 49 | const reqVideoFileAdd = createReqFiles( |
50 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | ||
51 | Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT), | ||
52 | { | ||
53 | videofile: CONFIG.STORAGE.VIDEOS_DIR, | ||
54 | thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, | ||
55 | previewfile: CONFIG.STORAGE.PREVIEWS_DIR | ||
56 | } | ||
57 | ) | ||
58 | const reqVideoFileUpdate = createReqFiles( | ||
59 | [ 'thumbnailfile', 'previewfile' ], | ||
60 | IMAGE_MIMETYPE_EXT, | ||
61 | { | ||
62 | thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, | ||
63 | previewfile: CONFIG.STORAGE.PREVIEWS_DIR | ||
64 | } | ||
65 | ) | ||
32 | 66 | ||
33 | videosRouter.use('/', abuseVideoRouter) | 67 | videosRouter.use('/', abuseVideoRouter) |
34 | videosRouter.use('/', blacklistRouter) | 68 | videosRouter.use('/', blacklistRouter) |
@@ -58,12 +92,13 @@ videosRouter.get('/search', | |||
58 | ) | 92 | ) |
59 | videosRouter.put('/:id', | 93 | videosRouter.put('/:id', |
60 | authenticate, | 94 | authenticate, |
95 | reqVideoFileUpdate, | ||
61 | asyncMiddleware(videosUpdateValidator), | 96 | asyncMiddleware(videosUpdateValidator), |
62 | asyncMiddleware(updateVideoRetryWrapper) | 97 | asyncMiddleware(updateVideoRetryWrapper) |
63 | ) | 98 | ) |
64 | videosRouter.post('/upload', | 99 | videosRouter.post('/upload', |
65 | authenticate, | 100 | authenticate, |
66 | reqVideoFile, | 101 | reqVideoFileAdd, |
67 | asyncMiddleware(videosAddValidator), | 102 | asyncMiddleware(videosAddValidator), |
68 | asyncMiddleware(addVideoRetryWrapper) | 103 | asyncMiddleware(addVideoRetryWrapper) |
69 | ) | 104 | ) |
@@ -150,8 +185,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
150 | const video = new VideoModel(videoData) | 185 | const video = new VideoModel(videoData) |
151 | video.url = getVideoActivityPubUrl(video) | 186 | video.url = getVideoActivityPubUrl(video) |
152 | 187 | ||
153 | const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) | 188 | const videoFileHeight = await getVideoFileHeight(videoPhysicalFile.path) |
154 | const videoFileHeight = await getVideoFileHeight(videoFilePath) | ||
155 | 189 | ||
156 | const videoFileData = { | 190 | const videoFileData = { |
157 | extname: extname(videoPhysicalFile.filename), | 191 | extname: extname(videoPhysicalFile.filename), |
@@ -160,21 +194,28 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
160 | } | 194 | } |
161 | const videoFile = new VideoFileModel(videoFileData) | 195 | const videoFile = new VideoFileModel(videoFileData) |
162 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR | 196 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR |
163 | const source = join(videoDir, videoPhysicalFile.filename) | ||
164 | const destination = join(videoDir, video.getVideoFilename(videoFile)) | 197 | const destination = join(videoDir, video.getVideoFilename(videoFile)) |
198 | await renamePromise(videoPhysicalFile.path, destination) | ||
165 | 199 | ||
166 | await renamePromise(source, destination) | 200 | // Process thumbnail or create it from the video |
167 | // This is important in case if there is another attempt in the retry process | 201 | const thumbnailField = req.files['thumbnailfile'] |
168 | videoPhysicalFile.filename = video.getVideoFilename(videoFile) | 202 | if (thumbnailField) { |
203 | const thumbnailPhysicalFile = thumbnailField[0] | ||
204 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) | ||
205 | } else { | ||
206 | await video.createThumbnail(videoFile) | ||
207 | } | ||
169 | 208 | ||
170 | const tasks = [] | 209 | // Process preview or create it from the video |
210 | const previewField = req.files['previewfile'] | ||
211 | if (previewField) { | ||
212 | const previewPhysicalFile = previewField[0] | ||
213 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) | ||
214 | } else { | ||
215 | await video.createPreview(videoFile) | ||
216 | } | ||
171 | 217 | ||
172 | tasks.push( | 218 | await video.createTorrentAndSetInfoHash(videoFile) |
173 | video.createTorrentAndSetInfoHash(videoFile), | ||
174 | video.createThumbnail(videoFile), | ||
175 | video.createPreview(videoFile) | ||
176 | ) | ||
177 | await Promise.all(tasks) | ||
178 | 219 | ||
179 | const videoCreated = await sequelizeTypescript.transaction(async t => { | 220 | const videoCreated = await sequelizeTypescript.transaction(async t => { |
180 | const sequelizeOptions = { transaction: t } | 221 | const sequelizeOptions = { transaction: t } |
@@ -237,6 +278,18 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
237 | const videoInfoToUpdate: VideoUpdate = req.body | 278 | const videoInfoToUpdate: VideoUpdate = req.body |
238 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE | 279 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE |
239 | 280 | ||
281 | // Process thumbnail or create it from the video | ||
282 | if (req.files && req.files['thumbnailfile']) { | ||
283 | const thumbnailPhysicalFile = req.files['thumbnailfile'][0] | ||
284 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE) | ||
285 | } | ||
286 | |||
287 | // Process preview or create it from the video | ||
288 | if (req.files && req.files['previewfile']) { | ||
289 | const previewPhysicalFile = req.files['previewfile'][0] | ||
290 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE) | ||
291 | } | ||
292 | |||
240 | try { | 293 | try { |
241 | await sequelizeTypescript.transaction(async t => { | 294 | await sequelizeTypescript.transaction(async t => { |
242 | const sequelizeOptions = { | 295 | const sequelizeOptions = { |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 3903884ea..8a270b777 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import 'multer' | ||
1 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
2 | 3 | ||
3 | function exists (value: any) { | 4 | function exists (value: any) { |
@@ -28,6 +29,29 @@ function isBooleanValid (value: string) { | |||
28 | return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) | 29 | return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) |
29 | } | 30 | } |
30 | 31 | ||
32 | function isFileValid ( | ||
33 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], | ||
34 | mimeTypeRegex: string, | ||
35 | field: string, | ||
36 | optional = false | ||
37 | ) { | ||
38 | // Should have files | ||
39 | if (!files) return optional | ||
40 | if (isArray(files)) return optional | ||
41 | |||
42 | // Should have a file | ||
43 | const fileArray = files[ field ] | ||
44 | if (!fileArray || fileArray.length === 0) { | ||
45 | return optional | ||
46 | } | ||
47 | |||
48 | // The file should exist | ||
49 | const file = fileArray[ 0 ] | ||
50 | if (!file || !file.originalname) return false | ||
51 | |||
52 | return new RegExp(`^${mimeTypeRegex}$`, 'i').test(file.mimetype) | ||
53 | } | ||
54 | |||
31 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
32 | 56 | ||
33 | export { | 57 | export { |
@@ -37,5 +61,6 @@ export { | |||
37 | isUUIDValid, | 61 | isUUIDValid, |
38 | isIdOrUUIDValid, | 62 | isIdOrUUIDValid, |
39 | isDateValid, | 63 | isDateValid, |
40 | isBooleanValid | 64 | isBooleanValid, |
65 | isFileValid | ||
41 | } | 66 | } |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 6ed60c1c4..e805313f8 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import 'express-validator' | 1 | import 'express-validator' |
3 | 2 | import * as validator from 'validator' | |
4 | import { exists, isArray } from './misc' | ||
5 | import { CONSTRAINTS_FIELDS } from '../../initializers' | ||
6 | import { UserRole } from '../../../shared' | 3 | import { UserRole } from '../../../shared' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers' | ||
5 | |||
6 | import { exists, isFileValid } from './misc' | ||
7 | 7 | ||
8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS | 8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS |
9 | 9 | ||
@@ -37,20 +37,12 @@ function isUserRoleValid (value: any) { | |||
37 | return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined | 37 | return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined |
38 | } | 38 | } |
39 | 39 | ||
40 | const avatarMimeTypes = CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME | ||
41 | .map(v => v.replace('.', '')) | ||
42 | .join('|') | ||
43 | const avatarMimeTypesRegex = `image/(${avatarMimeTypes})` | ||
40 | function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 44 | function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
41 | // Should have files | 45 | return isFileValid(files, avatarMimeTypesRegex, 'avatarfile') |
42 | if (!files) return false | ||
43 | if (isArray(files)) return false | ||
44 | |||
45 | // Should have videofile file | ||
46 | const avatarfile = files['avatarfile'] | ||
47 | if (!avatarfile || avatarfile.length === 0) return false | ||
48 | |||
49 | // The file should exist | ||
50 | const file = avatarfile[0] | ||
51 | if (!file || !file.originalname) return false | ||
52 | |||
53 | return new RegExp('^image/(png|jpeg)$', 'i').test(file.mimetype) | ||
54 | } | 46 | } |
55 | 47 | ||
56 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 0e8a2aab2..8ef3a3c64 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -8,12 +8,12 @@ import { | |||
8 | CONSTRAINTS_FIELDS, | 8 | CONSTRAINTS_FIELDS, |
9 | VIDEO_CATEGORIES, | 9 | VIDEO_CATEGORIES, |
10 | VIDEO_LANGUAGES, | 10 | VIDEO_LANGUAGES, |
11 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, |
12 | VIDEO_PRIVACIES, | 12 | VIDEO_PRIVACIES, |
13 | VIDEO_RATE_TYPES | 13 | VIDEO_RATE_TYPES |
14 | } from '../../initializers' | 14 | } from '../../initializers' |
15 | import { VideoModel } from '../../models/video/video' | 15 | import { VideoModel } from '../../models/video/video' |
16 | import { exists, isArray } from './misc' | 16 | import { exists, isArray, isFileValid } from './misc' |
17 | 17 | ||
18 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | 18 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS |
19 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | 19 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES |
@@ -68,20 +68,18 @@ function isVideoRatingTypeValid (value: string) { | |||
68 | return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 | 68 | return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 |
69 | } | 69 | } |
70 | 70 | ||
71 | const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`) | ||
72 | const videoFileTypesRegex = videoFileTypes.join('|') | ||
71 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 73 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
72 | // Should have files | 74 | return isFileValid(files, videoFileTypesRegex, 'videofile') |
73 | if (!files) return false | 75 | } |
74 | if (isArray(files)) return false | ||
75 | |||
76 | // Should have videofile file | ||
77 | const videofile = files['videofile'] | ||
78 | if (!videofile || videofile.length === 0) return false | ||
79 | |||
80 | // The file should exist | ||
81 | const file = videofile[0] | ||
82 | if (!file || !file.originalname) return false | ||
83 | 76 | ||
84 | return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype) | 77 | const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME |
78 | .map(v => v.replace('.', '')) | ||
79 | .join('|') | ||
80 | const videoImageTypesRegex = `image/(${videoImageTypes})` | ||
81 | function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { | ||
82 | return isFileValid(files, videoImageTypesRegex, field, true) | ||
85 | } | 83 | } |
86 | 84 | ||
87 | function isVideoPrivacyValid (value: string) { | 85 | function isVideoPrivacyValid (value: string) { |
@@ -141,5 +139,6 @@ export { | |||
141 | isVideoPrivacyValid, | 139 | isVideoPrivacyValid, |
142 | isVideoFileResolutionValid, | 140 | isVideoFileResolutionValid, |
143 | isVideoFileSizeValid, | 141 | isVideoFileSizeValid, |
144 | isVideoExist | 142 | isVideoExist, |
143 | isVideoImage | ||
145 | } | 144 | } |
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts new file mode 100644 index 000000000..ba57b5812 --- /dev/null +++ b/server/helpers/image-utils.ts | |||
@@ -0,0 +1,21 @@ | |||
1 | import 'multer' | ||
2 | import * as sharp from 'sharp' | ||
3 | import { unlinkPromise } from './core-utils' | ||
4 | |||
5 | async function processImage ( | ||
6 | physicalFile: Express.Multer.File, | ||
7 | destination: string, | ||
8 | newSize: { width: number, height: number } | ||
9 | ) { | ||
10 | await sharp(physicalFile.path) | ||
11 | .resize(newSize.width, newSize.height) | ||
12 | .toFile(destination) | ||
13 | |||
14 | await unlinkPromise(physicalFile.path) | ||
15 | } | ||
16 | |||
17 | // --------------------------------------------------------------------------- | ||
18 | |||
19 | export { | ||
20 | processImage | ||
21 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 79c3b5858..3b618360b 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -27,10 +27,14 @@ function badRequest (req: express.Request, res: express.Response, next: express. | |||
27 | return res.type('json').status(400).end() | 27 | return res.type('json').status(400).end() |
28 | } | 28 | } |
29 | 29 | ||
30 | function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ id: string ]: string }) { | 30 | function createReqFiles ( |
31 | fieldNames: string[], | ||
32 | mimeTypes: { [ id: string ]: string }, | ||
33 | destinations: { [ fieldName: string ]: string } | ||
34 | ) { | ||
31 | const storage = multer.diskStorage({ | 35 | const storage = multer.diskStorage({ |
32 | destination: (req, file, cb) => { | 36 | destination: (req, file, cb) => { |
33 | cb(null, storageDir) | 37 | cb(null, destinations[file.fieldname]) |
34 | }, | 38 | }, |
35 | 39 | ||
36 | filename: async (req, file, cb) => { | 40 | filename: async (req, file, cb) => { |
@@ -48,7 +52,15 @@ function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ i | |||
48 | } | 52 | } |
49 | }) | 53 | }) |
50 | 54 | ||
51 | return multer({ storage }).fields([{ name: fieldName, maxCount: 1 }]) | 55 | const fields = [] |
56 | for (const fieldName of fieldNames) { | ||
57 | fields.push({ | ||
58 | name: fieldName, | ||
59 | maxCount: 1 | ||
60 | }) | ||
61 | } | ||
62 | |||
63 | return multer({ storage }).fields(fields) | ||
52 | } | 64 | } |
53 | 65 | ||
54 | async function generateRandomString (size: number) { | 66 | async function generateRandomString (size: number) { |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e531c4c39..91fbbde75 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -182,6 +182,12 @@ const CONSTRAINTS_FIELDS = { | |||
182 | NAME: { min: 3, max: 120 }, // Length | 182 | NAME: { min: 3, max: 120 }, // Length |
183 | TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length | 183 | TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length |
184 | DESCRIPTION: { min: 3, max: 3000 }, // Length | 184 | DESCRIPTION: { min: 3, max: 3000 }, // Length |
185 | IMAGE: { | ||
186 | EXTNAME: [ '.jpg', '.jpeg' ], | ||
187 | FILE_SIZE: { | ||
188 | max: 2 * 1024 * 1024 // 2MB | ||
189 | } | ||
190 | }, | ||
185 | EXTNAME: [ '.mp4', '.ogv', '.webm' ], | 191 | EXTNAME: [ '.mp4', '.ogv', '.webm' ], |
186 | INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 | 192 | INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 |
187 | DURATION: { min: 1 }, // Number | 193 | DURATION: { min: 1 }, // Number |
@@ -285,7 +291,7 @@ const VIDEO_MIMETYPE_EXT = { | |||
285 | 'video/mp4': '.mp4' | 291 | 'video/mp4': '.mp4' |
286 | } | 292 | } |
287 | 293 | ||
288 | const AVATAR_MIMETYPE_EXT = { | 294 | const IMAGE_MIMETYPE_EXT = { |
289 | 'image/png': '.png', | 295 | 'image/png': '.png', |
290 | 'image/jpg': '.jpg', | 296 | 'image/jpg': '.jpg', |
291 | 'image/jpeg': '.jpg' | 297 | 'image/jpeg': '.jpg' |
@@ -427,7 +433,7 @@ export { | |||
427 | VIDEO_RATE_TYPES, | 433 | VIDEO_RATE_TYPES, |
428 | VIDEO_MIMETYPE_EXT, | 434 | VIDEO_MIMETYPE_EXT, |
429 | USER_PASSWORD_RESET_LIFETIME, | 435 | USER_PASSWORD_RESET_LIFETIME, |
430 | AVATAR_MIMETYPE_EXT, | 436 | IMAGE_MIMETYPE_EXT, |
431 | SCHEDULER_INTERVAL, | 437 | SCHEDULER_INTERVAL, |
432 | JOB_COMPLETED_LIFETIME | 438 | JOB_COMPLETED_LIFETIME |
433 | } | 439 | } |
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 712de7d0d..c3255d8ca 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -12,7 +12,7 @@ import { logger } from '../../helpers/logger' | |||
12 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' | 12 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' |
13 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 13 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' |
14 | import { getUrlFromWebfinger } from '../../helpers/webfinger' | 14 | import { getUrlFromWebfinger } from '../../helpers/webfinger' |
15 | import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers' | 15 | import { IMAGE_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers' |
16 | import { AccountModel } from '../../models/account/account' | 16 | import { AccountModel } from '../../models/account/account' |
17 | import { ActorModel } from '../../models/activitypub/actor' | 17 | import { ActorModel } from '../../models/activitypub/actor' |
18 | import { AvatarModel } from '../../models/avatar/avatar' | 18 | import { AvatarModel } from '../../models/avatar/avatar' |
@@ -147,10 +147,10 @@ async function fetchActorTotalItems (url: string) { | |||
147 | 147 | ||
148 | async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { | 148 | async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { |
149 | if ( | 149 | if ( |
150 | actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && | 150 | actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && |
151 | isActivityPubUrlValid(actorJSON.icon.url) | 151 | isActivityPubUrlValid(actorJSON.icon.url) |
152 | ) { | 152 | ) { |
153 | const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] | 153 | const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] |
154 | 154 | ||
155 | const avatarName = uuidv4() + extension | 155 | const avatarName = uuidv4() + extension |
156 | const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 156 | const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index a365ed217..6d4fb907b 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -4,8 +4,18 @@ import { body, param, query } from 'express-validator/check' | |||
4 | import { UserRight, VideoPrivacy } from '../../../shared' | 4 | import { UserRight, VideoPrivacy } from '../../../shared' |
5 | import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc' | 5 | import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
7 | isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid, | 7 | isVideoAbuseReasonValid, |
8 | isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid | 8 | isVideoCategoryValid, |
9 | isVideoDescriptionValid, | ||
10 | isVideoExist, | ||
11 | isVideoFile, | ||
12 | isVideoImage, | ||
13 | isVideoLanguageValid, | ||
14 | isVideoLicenceValid, | ||
15 | isVideoNameValid, | ||
16 | isVideoPrivacyValid, | ||
17 | isVideoRatingTypeValid, | ||
18 | isVideoTagsValid | ||
9 | } from '../../helpers/custom-validators/videos' | 19 | } from '../../helpers/custom-validators/videos' |
10 | import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' | 20 | import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' |
11 | import { logger } from '../../helpers/logger' | 21 | import { logger } from '../../helpers/logger' |
@@ -22,6 +32,14 @@ const videosAddValidator = [ | |||
22 | 'This file is not supported. Please, make sure it is of the following type : ' | 32 | 'This file is not supported. Please, make sure it is of the following type : ' |
23 | + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') | 33 | + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') |
24 | ), | 34 | ), |
35 | body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | ||
36 | 'This thumbnail file is not supported. Please, make sure it is of the following type : ' | ||
37 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
38 | ), | ||
39 | body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( | ||
40 | 'This preview file is not supported. Please, make sure it is of the following type : ' | ||
41 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
42 | ), | ||
25 | body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), | 43 | body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), |
26 | body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), | 44 | body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), |
27 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 45 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
@@ -37,6 +55,7 @@ const videosAddValidator = [ | |||
37 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) | 55 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) |
38 | 56 | ||
39 | if (areValidationErrors(req, res)) return | 57 | if (areValidationErrors(req, res)) return |
58 | if (areErrorsInVideoImageFiles(req, res)) return | ||
40 | 59 | ||
41 | const videoFile: Express.Multer.File = req.files['videofile'][0] | 60 | const videoFile: Express.Multer.File = req.files['videofile'][0] |
42 | const user = res.locals.oauth.token.User | 61 | const user = res.locals.oauth.token.User |
@@ -82,6 +101,14 @@ const videosAddValidator = [ | |||
82 | 101 | ||
83 | const videosUpdateValidator = [ | 102 | const videosUpdateValidator = [ |
84 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 103 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
104 | body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | ||
105 | 'This thumbnail file is not supported. Please, make sure it is of the following type : ' | ||
106 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
107 | ), | ||
108 | body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( | ||
109 | 'This preview file is not supported. Please, make sure it is of the following type : ' | ||
110 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
111 | ), | ||
85 | body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'), | 112 | body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'), |
86 | body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), | 113 | body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), |
87 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 114 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
@@ -96,6 +123,7 @@ const videosUpdateValidator = [ | |||
96 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) | 123 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) |
97 | 124 | ||
98 | if (areValidationErrors(req, res)) return | 125 | if (areValidationErrors(req, res)) return |
126 | if (areErrorsInVideoImageFiles(req, res)) return | ||
99 | if (!await isVideoExist(req.params.id, res)) return | 127 | if (!await isVideoExist(req.params.id, res)) return |
100 | 128 | ||
101 | const video = res.locals.video | 129 | const video = res.locals.video |
@@ -274,3 +302,22 @@ function checkUserCanDeleteVideo (user: UserModel, video: VideoModel, res: expre | |||
274 | 302 | ||
275 | return true | 303 | return true |
276 | } | 304 | } |
305 | |||
306 | function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) { | ||
307 | // Files are optional | ||
308 | if (!req.files) return false | ||
309 | |||
310 | for (const imageField of [ 'thumbnail', 'preview' ]) { | ||
311 | if (!req.files[ imageField ]) continue | ||
312 | |||
313 | const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File | ||
314 | if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) { | ||
315 | res.status(400) | ||
316 | .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` }) | ||
317 | .end() | ||
318 | return true | ||
319 | } | ||
320 | } | ||
321 | |||
322 | return false | ||
323 | } | ||
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 0fbc414c9..d9dea0713 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -7,7 +7,7 @@ import { UserRole } from '../../../../shared' | |||
7 | 7 | ||
8 | import { | 8 | import { |
9 | createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, | 9 | createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, |
10 | makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, | 10 | makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, |
11 | updateUser, uploadVideo, userLogin | 11 | updateUser, uploadVideo, userLogin |
12 | } from '../../utils' | 12 | } from '../../utils' |
13 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | 13 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' |
@@ -273,7 +273,7 @@ describe('Test users API validators', function () { | |||
273 | const attaches = { | 273 | const attaches = { |
274 | 'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4') | 274 | 'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4') |
275 | } | 275 | } |
276 | await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | 276 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) |
277 | }) | 277 | }) |
278 | 278 | ||
279 | it('Should fail with a big file', async function () { | 279 | it('Should fail with a big file', async function () { |
@@ -281,7 +281,7 @@ describe('Test users API validators', function () { | |||
281 | const attaches = { | 281 | const attaches = { |
282 | 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png') | 282 | 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png') |
283 | } | 283 | } |
284 | await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | 284 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) |
285 | }) | 285 | }) |
286 | 286 | ||
287 | it('Should succeed with the correct params', async function () { | 287 | it('Should succeed with the correct params', async function () { |
@@ -289,7 +289,7 @@ describe('Test users API validators', function () { | |||
289 | const attaches = { | 289 | const attaches = { |
290 | 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png') | 290 | 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png') |
291 | } | 291 | } |
292 | await makePostUploadRequest({ | 292 | await makeUploadRequest({ |
293 | url: server.url, | 293 | url: server.url, |
294 | path: path + '/me/avatar/pick', | 294 | path: path + '/me/avatar/pick', |
295 | token: server.accessToken, | 295 | token: server.accessToken, |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index f25e3f595..aa30b721b 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -7,7 +7,7 @@ import { join } from 'path' | |||
7 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 7 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' |
8 | import { | 8 | import { |
9 | createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest, | 9 | createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest, |
10 | makeGetRequest, makePostUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin | 10 | makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin |
11 | } from '../../utils' | 11 | } from '../../utils' |
12 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | 12 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' |
13 | 13 | ||
@@ -111,91 +111,91 @@ describe('Test videos API validator', function () { | |||
111 | it('Should fail with nothing', async function () { | 111 | it('Should fail with nothing', async function () { |
112 | const fields = {} | 112 | const fields = {} |
113 | const attaches = {} | 113 | const attaches = {} |
114 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 114 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
115 | }) | 115 | }) |
116 | 116 | ||
117 | it('Should fail without name', async function () { | 117 | it('Should fail without name', async function () { |
118 | const fields = omit(baseCorrectParams, 'name') | 118 | const fields = omit(baseCorrectParams, 'name') |
119 | const attaches = baseCorrectAttaches | 119 | const attaches = baseCorrectAttaches |
120 | 120 | ||
121 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 121 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
122 | }) | 122 | }) |
123 | 123 | ||
124 | it('Should fail with a long name', async function () { | 124 | it('Should fail with a long name', async function () { |
125 | const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) }) | 125 | const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) }) |
126 | const attaches = baseCorrectAttaches | 126 | const attaches = baseCorrectAttaches |
127 | 127 | ||
128 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 128 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
129 | }) | 129 | }) |
130 | 130 | ||
131 | it('Should fail with a bad category', async function () { | 131 | it('Should fail with a bad category', async function () { |
132 | const fields = immutableAssign(baseCorrectParams, { category: 125 }) | 132 | const fields = immutableAssign(baseCorrectParams, { category: 125 }) |
133 | const attaches = baseCorrectAttaches | 133 | const attaches = baseCorrectAttaches |
134 | 134 | ||
135 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 135 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
136 | }) | 136 | }) |
137 | 137 | ||
138 | it('Should fail with a bad licence', async function () { | 138 | it('Should fail with a bad licence', async function () { |
139 | const fields = immutableAssign(baseCorrectParams, { licence: 125 }) | 139 | const fields = immutableAssign(baseCorrectParams, { licence: 125 }) |
140 | const attaches = baseCorrectAttaches | 140 | const attaches = baseCorrectAttaches |
141 | 141 | ||
142 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 142 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
143 | }) | 143 | }) |
144 | 144 | ||
145 | it('Should fail with a bad language', async function () { | 145 | it('Should fail with a bad language', async function () { |
146 | const fields = immutableAssign(baseCorrectParams, { language: 125 }) | 146 | const fields = immutableAssign(baseCorrectParams, { language: 125 }) |
147 | const attaches = baseCorrectAttaches | 147 | const attaches = baseCorrectAttaches |
148 | 148 | ||
149 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 149 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
150 | }) | 150 | }) |
151 | 151 | ||
152 | it('Should fail without nsfw attribute', async function () { | 152 | it('Should fail without nsfw attribute', async function () { |
153 | const fields = omit(baseCorrectParams, 'nsfw') | 153 | const fields = omit(baseCorrectParams, 'nsfw') |
154 | const attaches = baseCorrectAttaches | 154 | const attaches = baseCorrectAttaches |
155 | 155 | ||
156 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 156 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
157 | }) | 157 | }) |
158 | 158 | ||
159 | it('Should fail with a bad nsfw attribute', async function () { | 159 | it('Should fail with a bad nsfw attribute', async function () { |
160 | const fields = immutableAssign(baseCorrectParams, { nsfw: 2 }) | 160 | const fields = immutableAssign(baseCorrectParams, { nsfw: 2 }) |
161 | const attaches = baseCorrectAttaches | 161 | const attaches = baseCorrectAttaches |
162 | 162 | ||
163 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 163 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
164 | }) | 164 | }) |
165 | 165 | ||
166 | it('Should fail without commentsEnabled attribute', async function () { | 166 | it('Should fail without commentsEnabled attribute', async function () { |
167 | const fields = omit(baseCorrectParams, 'commentsEnabled') | 167 | const fields = omit(baseCorrectParams, 'commentsEnabled') |
168 | const attaches = baseCorrectAttaches | 168 | const attaches = baseCorrectAttaches |
169 | 169 | ||
170 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 170 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
171 | }) | 171 | }) |
172 | 172 | ||
173 | it('Should fail with a bad commentsEnabled attribute', async function () { | 173 | it('Should fail with a bad commentsEnabled attribute', async function () { |
174 | const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 }) | 174 | const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 }) |
175 | const attaches = baseCorrectAttaches | 175 | const attaches = baseCorrectAttaches |
176 | 176 | ||
177 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 177 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
178 | }) | 178 | }) |
179 | 179 | ||
180 | it('Should fail with a long description', async function () { | 180 | it('Should fail with a long description', async function () { |
181 | const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) | 181 | const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) |
182 | const attaches = baseCorrectAttaches | 182 | const attaches = baseCorrectAttaches |
183 | 183 | ||
184 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 184 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
185 | }) | 185 | }) |
186 | 186 | ||
187 | it('Should fail without a channel', async function () { | 187 | it('Should fail without a channel', async function () { |
188 | const fields = omit(baseCorrectParams, 'channelId') | 188 | const fields = omit(baseCorrectParams, 'channelId') |
189 | const attaches = baseCorrectAttaches | 189 | const attaches = baseCorrectAttaches |
190 | 190 | ||
191 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 191 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
192 | }) | 192 | }) |
193 | 193 | ||
194 | it('Should fail with a bad channel', async function () { | 194 | it('Should fail with a bad channel', async function () { |
195 | const fields = immutableAssign(baseCorrectParams, { channelId: 545454 }) | 195 | const fields = immutableAssign(baseCorrectParams, { channelId: 545454 }) |
196 | const attaches = baseCorrectAttaches | 196 | const attaches = baseCorrectAttaches |
197 | 197 | ||
198 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 198 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
199 | }) | 199 | }) |
200 | 200 | ||
201 | it('Should fail with another user channel', async function () { | 201 | it('Should fail with another user channel', async function () { |
@@ -212,34 +212,34 @@ describe('Test videos API validator', function () { | |||
212 | const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId }) | 212 | const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId }) |
213 | const attaches = baseCorrectAttaches | 213 | const attaches = baseCorrectAttaches |
214 | 214 | ||
215 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 215 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
216 | }) | 216 | }) |
217 | 217 | ||
218 | it('Should fail with too many tags', async function () { | 218 | it('Should fail with too many tags', async function () { |
219 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }) | 219 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }) |
220 | const attaches = baseCorrectAttaches | 220 | const attaches = baseCorrectAttaches |
221 | 221 | ||
222 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 222 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
223 | }) | 223 | }) |
224 | 224 | ||
225 | it('Should fail with a tag length too low', async function () { | 225 | it('Should fail with a tag length too low', async function () { |
226 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] }) | 226 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] }) |
227 | const attaches = baseCorrectAttaches | 227 | const attaches = baseCorrectAttaches |
228 | 228 | ||
229 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 229 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
230 | }) | 230 | }) |
231 | 231 | ||
232 | it('Should fail with a tag length too big', async function () { | 232 | it('Should fail with a tag length too big', async function () { |
233 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }) | 233 | const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }) |
234 | const attaches = baseCorrectAttaches | 234 | const attaches = baseCorrectAttaches |
235 | 235 | ||
236 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 236 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
237 | }) | 237 | }) |
238 | 238 | ||
239 | it('Should fail without an input file', async function () { | 239 | it('Should fail without an input file', async function () { |
240 | const fields = baseCorrectParams | 240 | const fields = baseCorrectParams |
241 | const attaches = {} | 241 | const attaches = {} |
242 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 242 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
243 | }) | 243 | }) |
244 | 244 | ||
245 | it('Should fail without an incorrect input file', async function () { | 245 | it('Should fail without an incorrect input file', async function () { |
@@ -247,7 +247,47 @@ describe('Test videos API validator', function () { | |||
247 | const attaches = { | 247 | const attaches = { |
248 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | 248 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') |
249 | } | 249 | } |
250 | await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 250 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
251 | }) | ||
252 | |||
253 | it('Should fail with an incorrect thumbnail file', async function () { | ||
254 | const fields = baseCorrectParams | ||
255 | const attaches = { | ||
256 | 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png'), | ||
257 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | ||
258 | } | ||
259 | |||
260 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
261 | }) | ||
262 | |||
263 | it('Should fail with a big thumbnail file', async function () { | ||
264 | const fields = baseCorrectParams | ||
265 | const attaches = { | ||
266 | 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'), | ||
267 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | ||
268 | } | ||
269 | |||
270 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
271 | }) | ||
272 | |||
273 | it('Should fail with an incorrect preview file', async function () { | ||
274 | const fields = baseCorrectParams | ||
275 | const attaches = { | ||
276 | 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png'), | ||
277 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | ||
278 | } | ||
279 | |||
280 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
281 | }) | ||
282 | |||
283 | it('Should fail with a big preview file', async function () { | ||
284 | const fields = baseCorrectParams | ||
285 | const attaches = { | ||
286 | 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'), | ||
287 | 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | ||
288 | } | ||
289 | |||
290 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
251 | }) | 291 | }) |
252 | 292 | ||
253 | it('Should succeed with the correct parameters', async function () { | 293 | it('Should succeed with the correct parameters', async function () { |
@@ -257,7 +297,7 @@ describe('Test videos API validator', function () { | |||
257 | 297 | ||
258 | { | 298 | { |
259 | const attaches = baseCorrectAttaches | 299 | const attaches = baseCorrectAttaches |
260 | await makePostUploadRequest({ | 300 | await makeUploadRequest({ |
261 | url: server.url, | 301 | url: server.url, |
262 | path: path + '/upload', | 302 | path: path + '/upload', |
263 | token: server.accessToken, | 303 | token: server.accessToken, |
@@ -272,7 +312,7 @@ describe('Test videos API validator', function () { | |||
272 | videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4') | 312 | videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4') |
273 | }) | 313 | }) |
274 | 314 | ||
275 | await makePostUploadRequest({ | 315 | await makeUploadRequest({ |
276 | url: server.url, | 316 | url: server.url, |
277 | path: path + '/upload', | 317 | path: path + '/upload', |
278 | token: server.accessToken, | 318 | token: server.accessToken, |
@@ -287,7 +327,7 @@ describe('Test videos API validator', function () { | |||
287 | videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv') | 327 | videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv') |
288 | }) | 328 | }) |
289 | 329 | ||
290 | await makePostUploadRequest({ | 330 | await makeUploadRequest({ |
291 | url: server.url, | 331 | url: server.url, |
292 | path: path + '/upload', | 332 | path: path + '/upload', |
293 | token: server.accessToken, | 333 | token: server.accessToken, |
@@ -400,6 +440,70 @@ describe('Test videos API validator', function () { | |||
400 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | 440 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) |
401 | }) | 441 | }) |
402 | 442 | ||
443 | it('Should fail with an incorrect thumbnail file', async function () { | ||
444 | const fields = baseCorrectParams | ||
445 | const attaches = { | ||
446 | 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png') | ||
447 | } | ||
448 | |||
449 | await makeUploadRequest({ | ||
450 | url: server.url, | ||
451 | method: 'PUT', | ||
452 | path: path + videoId, | ||
453 | token: server.accessToken, | ||
454 | fields, | ||
455 | attaches | ||
456 | }) | ||
457 | }) | ||
458 | |||
459 | it('Should fail with a big thumbnail file', async function () { | ||
460 | const fields = baseCorrectParams | ||
461 | const attaches = { | ||
462 | 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png') | ||
463 | } | ||
464 | |||
465 | await makeUploadRequest({ | ||
466 | url: server.url, | ||
467 | method: 'PUT', | ||
468 | path: path + videoId, | ||
469 | token: server.accessToken, | ||
470 | fields, | ||
471 | attaches | ||
472 | }) | ||
473 | }) | ||
474 | |||
475 | it('Should fail with an incorrect preview file', async function () { | ||
476 | const fields = baseCorrectParams | ||
477 | const attaches = { | ||
478 | 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png') | ||
479 | } | ||
480 | |||
481 | await makeUploadRequest({ | ||
482 | url: server.url, | ||
483 | method: 'PUT', | ||
484 | path: path + videoId, | ||
485 | token: server.accessToken, | ||
486 | fields, | ||
487 | attaches | ||
488 | }) | ||
489 | }) | ||
490 | |||
491 | it('Should fail with a big preview file', async function () { | ||
492 | const fields = baseCorrectParams | ||
493 | const attaches = { | ||
494 | 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png') | ||
495 | } | ||
496 | |||
497 | await makeUploadRequest({ | ||
498 | url: server.url, | ||
499 | method: 'PUT', | ||
500 | path: path + videoId, | ||
501 | token: server.accessToken, | ||
502 | fields, | ||
503 | attaches | ||
504 | }) | ||
505 | }) | ||
506 | |||
403 | it('Should fail with a video of another user') | 507 | it('Should fail with a video of another user') |
404 | 508 | ||
405 | it('Should fail with a video of another server') | 509 | it('Should fail with a video of another server') |
diff --git a/server/tests/api/fixtures/preview.jpg b/server/tests/api/fixtures/preview.jpg new file mode 100644 index 000000000..c40ece838 --- /dev/null +++ b/server/tests/api/fixtures/preview.jpg | |||
Binary files differ | |||
diff --git a/server/tests/api/fixtures/thumbnail.jpg b/server/tests/api/fixtures/thumbnail.jpg new file mode 100644 index 000000000..cc3af8a4a --- /dev/null +++ b/server/tests/api/fixtures/thumbnail.jpg | |||
Binary files differ | |||
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 0215b3011..3646fbb0f 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -137,7 +137,9 @@ describe('Test multiple servers', function () { | |||
137 | nsfw: true, | 137 | nsfw: true, |
138 | description: 'my super description for server 2', | 138 | description: 'my super description for server 2', |
139 | tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], | 139 | tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], |
140 | fixture: 'video_short2.webm' | 140 | fixture: 'video_short2.webm', |
141 | thumbnailfile: 'thumbnail.jpg', | ||
142 | previewfile: 'preview.jpg' | ||
141 | } | 143 | } |
142 | await uploadVideo(servers[1].url, userAccessToken, videoAttributes) | 144 | await uploadVideo(servers[1].url, userAccessToken, videoAttributes) |
143 | 145 | ||
@@ -184,7 +186,9 @@ describe('Test multiple servers', function () { | |||
184 | resolution: 720, | 186 | resolution: 720, |
185 | size: 710000 | 187 | size: 710000 |
186 | } | 188 | } |
187 | ] | 189 | ], |
190 | thumbnailfile: 'thumbnail', | ||
191 | previewfile: 'preview' | ||
188 | } | 192 | } |
189 | 193 | ||
190 | const res = await getVideosList(server.url) | 194 | const res = await getVideosList(server.url) |
@@ -521,7 +525,9 @@ describe('Test multiple servers', function () { | |||
521 | language: 13, | 525 | language: 13, |
522 | nsfw: true, | 526 | nsfw: true, |
523 | description: 'my super description updated', | 527 | description: 'my super description updated', |
524 | tags: [ 'tag_up_1', 'tag_up_2' ] | 528 | tags: [ 'tag_up_1', 'tag_up_2' ], |
529 | thumbnailfile: 'thumbnail.jpg', | ||
530 | previewfile: 'preview.jpg' | ||
525 | } | 531 | } |
526 | 532 | ||
527 | await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes) | 533 | await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes) |
@@ -565,7 +571,9 @@ describe('Test multiple servers', function () { | |||
565 | resolution: 720, | 571 | resolution: 720, |
566 | size: 292677 | 572 | size: 292677 |
567 | } | 573 | } |
568 | ] | 574 | ], |
575 | thumbnailfile: 'thumbnail', | ||
576 | previewfile: 'preview' | ||
569 | } | 577 | } |
570 | await completeVideoCheck(server.url, videoUpdated, checkAttributes) | 578 | await completeVideoCheck(server.url, videoUpdated, checkAttributes) |
571 | } | 579 | } |
diff --git a/server/tests/utils/miscs/miscs.ts b/server/tests/utils/miscs/miscs.ts index 99d109bfe..24cbf59ca 100644 --- a/server/tests/utils/miscs/miscs.ts +++ b/server/tests/utils/miscs/miscs.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* tslint:disable:no-unused-expression */ |
2 | 2 | ||
3 | import { join } from 'path' | 3 | import { isAbsolute, join } from 'path' |
4 | import * as request from 'supertest' | 4 | import * as request from 'supertest' |
5 | import * as WebTorrent from 'webtorrent' | 5 | import * as WebTorrent from 'webtorrent' |
6 | import { readFileBufferPromise } from '../../../helpers/core-utils' | 6 | import { readFileBufferPromise } from '../../../helpers/core-utils' |
@@ -45,8 +45,8 @@ async function testImage (url: string, imageName: string, imagePath: string, ext | |||
45 | const body = res.body | 45 | const body = res.body |
46 | 46 | ||
47 | const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension)) | 47 | const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension)) |
48 | const minLength = body.length - ((50 * body.length) / 100) | 48 | const minLength = body.length - ((20 * body.length) / 100) |
49 | const maxLength = body.length + ((50 * body.length) / 100) | 49 | const maxLength = body.length + ((20 * body.length) / 100) |
50 | 50 | ||
51 | return data.length > minLength && data.length < maxLength | 51 | return data.length > minLength && data.length < maxLength |
52 | } else { | 52 | } else { |
@@ -55,6 +55,14 @@ async function testImage (url: string, imageName: string, imagePath: string, ext | |||
55 | } | 55 | } |
56 | } | 56 | } |
57 | 57 | ||
58 | function buildAbsoluteFixturePath (path: string) { | ||
59 | if (isAbsolute(path)) { | ||
60 | return path | ||
61 | } | ||
62 | |||
63 | return join(__dirname, '..', '..', 'api', 'fixtures', path) | ||
64 | } | ||
65 | |||
58 | // --------------------------------------------------------------------------- | 66 | // --------------------------------------------------------------------------- |
59 | 67 | ||
60 | export { | 68 | export { |
@@ -63,5 +71,6 @@ export { | |||
63 | webtorrentAdd, | 71 | webtorrentAdd, |
64 | immutableAssign, | 72 | immutableAssign, |
65 | testImage, | 73 | testImage, |
74 | buildAbsoluteFixturePath, | ||
66 | root | 75 | root |
67 | } | 76 | } |
diff --git a/server/tests/utils/requests/requests.ts b/server/tests/utils/requests/requests.ts index 840072430..a9b1dff9a 100644 --- a/server/tests/utils/requests/requests.ts +++ b/server/tests/utils/requests/requests.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { buildAbsoluteFixturePath } from '../' | ||
2 | 3 | ||
3 | function makeGetRequest (options: { | 4 | function makeGetRequest (options: { |
4 | url: string, | 5 | url: string, |
@@ -40,8 +41,9 @@ function makeDeleteRequest (options: { | |||
40 | .expect(options.statusCodeExpected) | 41 | .expect(options.statusCodeExpected) |
41 | } | 42 | } |
42 | 43 | ||
43 | function makePostUploadRequest (options: { | 44 | function makeUploadRequest (options: { |
44 | url: string, | 45 | url: string, |
46 | method?: 'POST' | 'PUT', | ||
45 | path: string, | 47 | path: string, |
46 | token: string, | 48 | token: string, |
47 | fields: { [ fieldName: string ]: any }, | 49 | fields: { [ fieldName: string ]: any }, |
@@ -50,9 +52,14 @@ function makePostUploadRequest (options: { | |||
50 | }) { | 52 | }) { |
51 | if (!options.statusCodeExpected) options.statusCodeExpected = 400 | 53 | if (!options.statusCodeExpected) options.statusCodeExpected = 400 |
52 | 54 | ||
53 | const req = request(options.url) | 55 | let req: request.Test |
54 | .post(options.path) | 56 | if (options.method === 'PUT') { |
55 | .set('Accept', 'application/json') | 57 | req = request(options.url).put(options.path) |
58 | } else { | ||
59 | req = request(options.url).post(options.path) | ||
60 | } | ||
61 | |||
62 | req.set('Accept', 'application/json') | ||
56 | 63 | ||
57 | if (options.token) req.set('Authorization', 'Bearer ' + options.token) | 64 | if (options.token) req.set('Authorization', 'Bearer ' + options.token) |
58 | 65 | ||
@@ -70,7 +77,7 @@ function makePostUploadRequest (options: { | |||
70 | 77 | ||
71 | Object.keys(options.attaches).forEach(attach => { | 78 | Object.keys(options.attaches).forEach(attach => { |
72 | const value = options.attaches[attach] | 79 | const value = options.attaches[attach] |
73 | req.attach(attach, value) | 80 | req.attach(attach, buildAbsoluteFixturePath(value)) |
74 | }) | 81 | }) |
75 | 82 | ||
76 | return req.expect(options.statusCodeExpected) | 83 | return req.expect(options.statusCodeExpected) |
@@ -119,7 +126,7 @@ function makePutBodyRequest (options: { | |||
119 | 126 | ||
120 | export { | 127 | export { |
121 | makeGetRequest, | 128 | makeGetRequest, |
122 | makePostUploadRequest, | 129 | makeUploadRequest, |
123 | makePostBodyRequest, | 130 | makePostBodyRequest, |
124 | makePutBodyRequest, | 131 | makePutBodyRequest, |
125 | makeDeleteRequest | 132 | makeDeleteRequest |
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts index 9e33e6796..3c9d46246 100644 --- a/server/tests/utils/users/users.ts +++ b/server/tests/utils/users/users.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { isAbsolute, join } from 'path' | 1 | import { isAbsolute, join } from 'path' |
2 | import * as request from 'supertest' | 2 | import * as request from 'supertest' |
3 | import { makePostBodyRequest, makePostUploadRequest, makePutBodyRequest } from '../' | 3 | import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../' |
4 | 4 | ||
5 | import { UserRole } from '../../../../shared/index' | 5 | import { UserRole } from '../../../../shared/index' |
6 | 6 | ||
@@ -162,7 +162,7 @@ function updateMyAvatar (options: { | |||
162 | filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture) | 162 | filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture) |
163 | } | 163 | } |
164 | 164 | ||
165 | return makePostUploadRequest({ | 165 | return makeUploadRequest({ |
166 | url: options.url, | 166 | url: options.url, |
167 | path, | 167 | path, |
168 | token: options.accessToken, | 168 | token: options.accessToken, |
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index 9105b5f13..9d4267db8 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -5,7 +5,16 @@ import { existsSync, readFile } from 'fs' | |||
5 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
6 | import { extname, isAbsolute, join } from 'path' | 6 | import { extname, isAbsolute, join } from 'path' |
7 | import * as request from 'supertest' | 7 | import * as request from 'supertest' |
8 | import { getMyUserInformation, makeGetRequest, root, ServerInfo, testImage } from '../' | 8 | import { |
9 | buildAbsoluteFixturePath, | ||
10 | getMyUserInformation, | ||
11 | makeGetRequest, | ||
12 | makePutBodyRequest, | ||
13 | makeUploadRequest, | ||
14 | root, | ||
15 | ServerInfo, | ||
16 | testImage | ||
17 | } from '../' | ||
9 | import { VideoPrivacy } from '../../../../shared/models/videos' | 18 | import { VideoPrivacy } from '../../../../shared/models/videos' |
10 | import { readdirPromise } from '../../../helpers/core-utils' | 19 | import { readdirPromise } from '../../../helpers/core-utils' |
11 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers' | 20 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers' |
@@ -23,6 +32,8 @@ type VideoAttributes = { | |||
23 | channelId?: number | 32 | channelId?: number |
24 | privacy?: VideoPrivacy | 33 | privacy?: VideoPrivacy |
25 | fixture?: string | 34 | fixture?: string |
35 | thumbnailfile?: string | ||
36 | previewfile?: string | ||
26 | } | 37 | } |
27 | 38 | ||
28 | function getVideoCategories (url: string) { | 39 | function getVideoCategories (url: string) { |
@@ -228,8 +239,8 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
228 | defaultChannelId = res.body.videoChannels[0].id | 239 | defaultChannelId = res.body.videoChannels[0].id |
229 | } catch (e) { /* empty */ } | 240 | } catch (e) { /* empty */ } |
230 | 241 | ||
231 | // Default attributes | 242 | // Override default attributes |
232 | let attributes = { | 243 | const attributes = Object.assign({ |
233 | name: 'my super video', | 244 | name: 'my super video', |
234 | category: 5, | 245 | category: 5, |
235 | licence: 4, | 246 | licence: 4, |
@@ -241,8 +252,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
241 | privacy: VideoPrivacy.PUBLIC, | 252 | privacy: VideoPrivacy.PUBLIC, |
242 | commentsEnabled: true, | 253 | commentsEnabled: true, |
243 | fixture: 'video_short.webm' | 254 | fixture: 'video_short.webm' |
244 | } | 255 | }, videoAttributesArg) |
245 | attributes = Object.assign(attributes, videoAttributesArg) | ||
246 | 256 | ||
247 | const req = request(url) | 257 | const req = request(url) |
248 | .post(path) | 258 | .post(path) |
@@ -267,22 +277,22 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
267 | req.field('licence', attributes.licence.toString()) | 277 | req.field('licence', attributes.licence.toString()) |
268 | } | 278 | } |
269 | 279 | ||
270 | for (let i = 0; i < attributes.tags.length; i++) { | 280 | if (attributes.thumbnailfile !== undefined) { |
271 | req.field('tags[' + i + ']', attributes.tags[i]) | 281 | req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile)) |
282 | } | ||
283 | if (attributes.previewfile !== undefined) { | ||
284 | req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile)) | ||
272 | } | 285 | } |
273 | 286 | ||
274 | let filePath = '' | 287 | for (let i = 0; i < attributes.tags.length; i++) { |
275 | if (isAbsolute(attributes.fixture)) { | 288 | req.field('tags[' + i + ']', attributes.tags[i]) |
276 | filePath = attributes.fixture | ||
277 | } else { | ||
278 | filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture) | ||
279 | } | 289 | } |
280 | 290 | ||
281 | return req.attach('videofile', filePath) | 291 | return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) |
282 | .expect(specialStatus) | 292 | .expect(specialStatus) |
283 | } | 293 | } |
284 | 294 | ||
285 | function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) { | 295 | function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) { |
286 | const path = '/api/v1/videos/' + id | 296 | const path = '/api/v1/videos/' + id |
287 | const body = {} | 297 | const body = {} |
288 | 298 | ||
@@ -296,12 +306,30 @@ function updateVideo (url: string, accessToken: string, id: number | string, att | |||
296 | if (attributes.tags) body['tags'] = attributes.tags | 306 | if (attributes.tags) body['tags'] = attributes.tags |
297 | if (attributes.privacy) body['privacy'] = attributes.privacy | 307 | if (attributes.privacy) body['privacy'] = attributes.privacy |
298 | 308 | ||
299 | return request(url) | 309 | // Upload request |
300 | .put(path) | 310 | if (attributes.thumbnailfile || attributes.previewfile) { |
301 | .send(body) | 311 | const attaches: any = {} |
302 | .set('Accept', 'application/json') | 312 | if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile |
303 | .set('Authorization', 'Bearer ' + accessToken) | 313 | if (attributes.previewfile) attaches.previewfile = attributes.previewfile |
304 | .expect(specialStatus) | 314 | |
315 | return makeUploadRequest({ | ||
316 | url, | ||
317 | method: 'PUT', | ||
318 | path, | ||
319 | token: accessToken, | ||
320 | fields: body, | ||
321 | attaches, | ||
322 | statusCodeExpected | ||
323 | }) | ||
324 | } | ||
325 | |||
326 | return makePutBodyRequest({ | ||
327 | url, | ||
328 | path, | ||
329 | fields: body, | ||
330 | token: accessToken, | ||
331 | statusCodeExpected | ||
332 | }) | ||
305 | } | 333 | } |
306 | 334 | ||
307 | function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) { | 335 | function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) { |
@@ -355,7 +383,9 @@ async function completeVideoCheck ( | |||
355 | files: { | 383 | files: { |
356 | resolution: number | 384 | resolution: number |
357 | size: number | 385 | size: number |
358 | }[] | 386 | }[], |
387 | thumbnailfile?: string | ||
388 | previewfile?: string | ||
359 | } | 389 | } |
360 | ) { | 390 | ) { |
361 | if (!attributes.likes) attributes.likes = 0 | 391 | if (!attributes.likes) attributes.likes = 0 |
@@ -414,8 +444,15 @@ async function completeVideoCheck ( | |||
414 | const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) | 444 | const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) |
415 | expect(file.size).to.be.above(minSize).and.below(maxSize) | 445 | expect(file.size).to.be.above(minSize).and.below(maxSize) |
416 | 446 | ||
417 | const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath) | 447 | { |
418 | expect(test).to.equal(true) | 448 | const test = await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) |
449 | expect(test).to.equal(true) | ||
450 | } | ||
451 | |||
452 | if (attributes.previewfile) { | ||
453 | const test = await testImage(url, attributes.previewfile, videoDetails.previewPath) | ||
454 | expect(test).to.equal(true) | ||
455 | } | ||
419 | 456 | ||
420 | const torrent = await webtorrentAdd(magnetUri, true) | 457 | const torrent = await webtorrentAdd(magnetUri, true) |
421 | expect(torrent.files).to.be.an('array') | 458 | expect(torrent.files).to.be.an('array') |
diff --git a/server/tools/import-youtube.ts b/server/tools/import-youtube.ts index 96bce29b5..ccbc71029 100644 --- a/server/tools/import-youtube.ts +++ b/server/tools/import-youtube.ts | |||
@@ -1,7 +1,5 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import { createWriteStream } from 'fs' | ||
3 | import { join } from 'path' | 2 | import { join } from 'path' |
4 | import { cursorTo } from 'readline' | ||
5 | import * as youtubeDL from 'youtube-dl' | 3 | import * as youtubeDL from 'youtube-dl' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 4 | import { VideoPrivacy } from '../../shared/models/videos' |
7 | import { unlinkPromise } from '../helpers/core-utils' | 5 | import { unlinkPromise } from '../helpers/core-utils' |