-import { ElementRef, OnInit, ViewChild, ViewChildren } from '@angular/core'
+import { ElementRef, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { isInMobileView } from '@app/shared/misc/utils'
import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
})
app.use(function (err, req, res, next) {
- logger.error(err, err)
+ logger.error('Error in controller.', { error: err.stack || err.message || err })
res.sendStatus(err.status || 500)
})
import * as express from 'express'
+import 'multer'
import { extname, join } from 'path'
-import * as sharp from 'sharp'
import * as uuidv4 from 'uuid/v4'
import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
-import { unlinkPromise } from '../../helpers/core-utils'
import { retryTransactionWrapper } from '../../helpers/database-utils'
+import { processImage } from '../../helpers/image-utils'
import { logger } from '../../helpers/logger'
import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
-import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
+import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
import { updateActorAvatarInstance } from '../../lib/activitypub'
import { sendUpdateUser } from '../../lib/activitypub/send'
import { Emailer } from '../../lib/emailer'
import { OAuthTokenModel } from '../../models/oauth/oauth-token'
import { VideoModel } from '../../models/video/video'
-const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT)
+const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const usersRouter = express.Router()
const user = res.locals.oauth.token.user
const actor = user.Account.Actor
- const avatarDir = CONFIG.STORAGE.AVATARS_DIR
- const source = join(avatarDir, avatarPhysicalFile.filename)
const extension = extname(avatarPhysicalFile.filename)
const avatarName = uuidv4() + extension
- const destination = join(avatarDir, avatarName)
-
- await sharp(source)
- .resize(AVATARS_SIZE.width, AVATARS_SIZE.height)
- .toFile(destination)
-
- await unlinkPromise(source)
+ const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
+ await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
const avatar = await sequelizeTypescript.transaction(async t => {
const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
import { renamePromise } from '../../../helpers/core-utils'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils'
+import { processImage } from '../../../helpers/image-utils'
import { logger } from '../../../helpers/logger'
import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
import {
- CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
+ CONFIG,
+ IMAGE_MIMETYPE_EXT,
+ PREVIEWS_SIZE,
+ sequelizeTypescript,
+ THUMBNAILS_SIZE,
+ VIDEO_CATEGORIES,
+ VIDEO_LANGUAGES,
+ VIDEO_LICENCES,
+ VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES
} from '../../../initializers'
import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
import { JobQueue } from '../../../lib/job-queue'
import {
- asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator,
- videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator
+ asyncMiddleware,
+ authenticate,
+ paginationValidator,
+ setDefaultPagination,
+ setDefaultSort,
+ videosAddValidator,
+ videosGetValidator,
+ videosRemoveValidator,
+ videosSearchValidator,
+ videosSortValidator,
+ videosUpdateValidator
} from '../../../middlewares'
import { TagModel } from '../../../models/video/tag'
import { VideoModel } from '../../../models/video/video'
const videosRouter = express.Router()
-const reqVideoFile = createReqFiles('videofile', CONFIG.STORAGE.VIDEOS_DIR, VIDEO_MIMETYPE_EXT)
+const reqVideoFileAdd = createReqFiles(
+ [ 'videofile', 'thumbnailfile', 'previewfile' ],
+ Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
+ {
+ videofile: CONFIG.STORAGE.VIDEOS_DIR,
+ thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
+ previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+ }
+)
+const reqVideoFileUpdate = createReqFiles(
+ [ 'thumbnailfile', 'previewfile' ],
+ IMAGE_MIMETYPE_EXT,
+ {
+ thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
+ previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+ }
+)
videosRouter.use('/', abuseVideoRouter)
videosRouter.use('/', blacklistRouter)
)
videosRouter.put('/:id',
authenticate,
+ reqVideoFileUpdate,
asyncMiddleware(videosUpdateValidator),
asyncMiddleware(updateVideoRetryWrapper)
)
videosRouter.post('/upload',
authenticate,
- reqVideoFile,
+ reqVideoFileAdd,
asyncMiddleware(videosAddValidator),
asyncMiddleware(addVideoRetryWrapper)
)
const video = new VideoModel(videoData)
video.url = getVideoActivityPubUrl(video)
- const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
- const videoFileHeight = await getVideoFileHeight(videoFilePath)
+ const videoFileHeight = await getVideoFileHeight(videoPhysicalFile.path)
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
}
const videoFile = new VideoFileModel(videoFileData)
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
- const source = join(videoDir, videoPhysicalFile.filename)
const destination = join(videoDir, video.getVideoFilename(videoFile))
+ await renamePromise(videoPhysicalFile.path, destination)
- await renamePromise(source, destination)
- // This is important in case if there is another attempt in the retry process
- videoPhysicalFile.filename = video.getVideoFilename(videoFile)
+ // Process thumbnail or create it from the video
+ const thumbnailField = req.files['thumbnailfile']
+ if (thumbnailField) {
+ const thumbnailPhysicalFile = thumbnailField[0]
+ await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
+ } else {
+ await video.createThumbnail(videoFile)
+ }
- const tasks = []
+ // Process preview or create it from the video
+ const previewField = req.files['previewfile']
+ if (previewField) {
+ const previewPhysicalFile = previewField[0]
+ await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
+ } else {
+ await video.createPreview(videoFile)
+ }
- tasks.push(
- video.createTorrentAndSetInfoHash(videoFile),
- video.createThumbnail(videoFile),
- video.createPreview(videoFile)
- )
- await Promise.all(tasks)
+ await video.createTorrentAndSetInfoHash(videoFile)
const videoCreated = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
const videoInfoToUpdate: VideoUpdate = req.body
const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
+ // Process thumbnail or create it from the video
+ if (req.files && req.files['thumbnailfile']) {
+ const thumbnailPhysicalFile = req.files['thumbnailfile'][0]
+ await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE)
+ }
+
+ // Process preview or create it from the video
+ if (req.files && req.files['previewfile']) {
+ const previewPhysicalFile = req.files['previewfile'][0]
+ await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE)
+ }
+
try {
await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = {
+import 'multer'
import * as validator from 'validator'
function exists (value: any) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
}
+function isFileValid (
+ files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
+ mimeTypeRegex: string,
+ field: string,
+ optional = false
+) {
+ // Should have files
+ if (!files) return optional
+ if (isArray(files)) return optional
+
+ // Should have a file
+ const fileArray = files[ field ]
+ if (!fileArray || fileArray.length === 0) {
+ return optional
+ }
+
+ // The file should exist
+ const file = fileArray[ 0 ]
+ if (!file || !file.originalname) return false
+
+ return new RegExp(`^${mimeTypeRegex}$`, 'i').test(file.mimetype)
+}
+
// ---------------------------------------------------------------------------
export {
isUUIDValid,
isIdOrUUIDValid,
isDateValid,
- isBooleanValid
+ isBooleanValid,
+ isFileValid
}
-import * as validator from 'validator'
import 'express-validator'
-
-import { exists, isArray } from './misc'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
+import * as validator from 'validator'
import { UserRole } from '../../../shared'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
+
+import { exists, isFileValid } from './misc'
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
}
+const avatarMimeTypes = CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
+ .map(v => v.replace('.', ''))
+ .join('|')
+const avatarMimeTypesRegex = `image/(${avatarMimeTypes})`
function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
- // Should have files
- if (!files) return false
- if (isArray(files)) return false
-
- // Should have videofile file
- const avatarfile = files['avatarfile']
- if (!avatarfile || avatarfile.length === 0) return false
-
- // The file should exist
- const file = avatarfile[0]
- if (!file || !file.originalname) return false
-
- return new RegExp('^image/(png|jpeg)$', 'i').test(file.mimetype)
+ return isFileValid(files, avatarMimeTypesRegex, 'avatarfile')
}
// ---------------------------------------------------------------------------
CONSTRAINTS_FIELDS,
VIDEO_CATEGORIES,
VIDEO_LANGUAGES,
- VIDEO_LICENCES,
+ VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES,
VIDEO_RATE_TYPES
} from '../../initializers'
import { VideoModel } from '../../models/video/video'
-import { exists, isArray } from './misc'
+import { exists, isArray, isFileValid } from './misc'
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
}
+const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
+const videoFileTypesRegex = videoFileTypes.join('|')
function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
- // Should have files
- if (!files) return false
- if (isArray(files)) return false
-
- // Should have videofile file
- const videofile = files['videofile']
- if (!videofile || videofile.length === 0) return false
-
- // The file should exist
- const file = videofile[0]
- if (!file || !file.originalname) return false
+ return isFileValid(files, videoFileTypesRegex, 'videofile')
+}
- return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype)
+const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
+ .map(v => v.replace('.', ''))
+ .join('|')
+const videoImageTypesRegex = `image/(${videoImageTypes})`
+function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
+ return isFileValid(files, videoImageTypesRegex, field, true)
}
function isVideoPrivacyValid (value: string) {
isVideoPrivacyValid,
isVideoFileResolutionValid,
isVideoFileSizeValid,
- isVideoExist
+ isVideoExist,
+ isVideoImage
}
--- /dev/null
+import 'multer'
+import * as sharp from 'sharp'
+import { unlinkPromise } from './core-utils'
+
+async function processImage (
+ physicalFile: Express.Multer.File,
+ destination: string,
+ newSize: { width: number, height: number }
+) {
+ await sharp(physicalFile.path)
+ .resize(newSize.width, newSize.height)
+ .toFile(destination)
+
+ await unlinkPromise(physicalFile.path)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processImage
+}
return res.type('json').status(400).end()
}
-function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ id: string ]: string }) {
+function createReqFiles (
+ fieldNames: string[],
+ mimeTypes: { [ id: string ]: string },
+ destinations: { [ fieldName: string ]: string }
+) {
const storage = multer.diskStorage({
destination: (req, file, cb) => {
- cb(null, storageDir)
+ cb(null, destinations[file.fieldname])
},
filename: async (req, file, cb) => {
}
})
- return multer({ storage }).fields([{ name: fieldName, maxCount: 1 }])
+ const fields = []
+ for (const fieldName of fieldNames) {
+ fields.push({
+ name: fieldName,
+ maxCount: 1
+ })
+ }
+
+ return multer({ storage }).fields(fields)
}
async function generateRandomString (size: number) {
NAME: { min: 3, max: 120 }, // Length
TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
DESCRIPTION: { min: 3, max: 3000 }, // Length
+ IMAGE: {
+ EXTNAME: [ '.jpg', '.jpeg' ],
+ FILE_SIZE: {
+ max: 2 * 1024 * 1024 // 2MB
+ }
+ },
EXTNAME: [ '.mp4', '.ogv', '.webm' ],
INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
DURATION: { min: 1 }, // Number
'video/mp4': '.mp4'
}
-const AVATAR_MIMETYPE_EXT = {
+const IMAGE_MIMETYPE_EXT = {
'image/png': '.png',
'image/jpg': '.jpg',
'image/jpeg': '.jpg'
VIDEO_RATE_TYPES,
VIDEO_MIMETYPE_EXT,
USER_PASSWORD_RESET_LIFETIME,
- AVATAR_MIMETYPE_EXT,
+ IMAGE_MIMETYPE_EXT,
SCHEDULER_INTERVAL,
JOB_COMPLETED_LIFETIME
}
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
import { getUrlFromWebfinger } from '../../helpers/webfinger'
-import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
+import { IMAGE_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
import { AccountModel } from '../../models/account/account'
import { ActorModel } from '../../models/activitypub/actor'
import { AvatarModel } from '../../models/avatar/avatar'
async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
if (
- actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
+ actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
isActivityPubUrlValid(actorJSON.icon.url)
) {
- const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType]
+ const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType]
const avatarName = uuidv4() + extension
const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
import { UserRight, VideoPrivacy } from '../../../shared'
import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc'
import {
- isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid,
- isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid
+ isVideoAbuseReasonValid,
+ isVideoCategoryValid,
+ isVideoDescriptionValid,
+ isVideoExist,
+ isVideoFile,
+ isVideoImage,
+ isVideoLanguageValid,
+ isVideoLicenceValid,
+ isVideoNameValid,
+ isVideoPrivacyValid,
+ isVideoRatingTypeValid,
+ isVideoTagsValid
} from '../../helpers/custom-validators/videos'
import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger'
'This file is not supported. Please, make sure it is of the following type : '
+ CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
),
+ body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
+ 'This thumbnail file is not supported. Please, make sure it is of the following type : '
+ + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+ ),
+ body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
+ 'This preview file is not supported. Please, make sure it is of the following type : '
+ + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+ ),
body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
if (areValidationErrors(req, res)) return
+ if (areErrorsInVideoImageFiles(req, res)) return
const videoFile: Express.Multer.File = req.files['videofile'][0]
const user = res.locals.oauth.token.User
const videosUpdateValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+ body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
+ 'This thumbnail file is not supported. Please, make sure it is of the following type : '
+ + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+ ),
+ body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
+ 'This preview file is not supported. Please, make sure it is of the following type : '
+ + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+ ),
body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
logger.debug('Checking videosUpdate parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
+ if (areErrorsInVideoImageFiles(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
const video = res.locals.video
return true
}
+
+function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
+ // Files are optional
+ if (!req.files) return false
+
+ for (const imageField of [ 'thumbnail', 'preview' ]) {
+ if (!req.files[ imageField ]) continue
+
+ const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
+ if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
+ res.status(400)
+ .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })
+ .end()
+ return true
+ }
+ }
+
+ return false
+}
import {
createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
- makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
+ makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
updateUser, uploadVideo, userLogin
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
const attaches = {
'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4')
}
- await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
})
it('Should fail with a big file', async function () {
const attaches = {
'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
}
- await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
})
it('Should succeed with the correct params', async function () {
const attaches = {
'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png')
}
- await makePostUploadRequest({
+ await makeUploadRequest({
url: server.url,
path: path + '/me/avatar/pick',
token: server.accessToken,
import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
import {
createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
- makeGetRequest, makePostUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
+ makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
it('Should fail with nothing', async function () {
const fields = {}
const attaches = {}
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without name', async function () {
const fields = omit(baseCorrectParams, 'name')
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a long name', async function () {
const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad category', async function () {
const fields = immutableAssign(baseCorrectParams, { category: 125 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad licence', async function () {
const fields = immutableAssign(baseCorrectParams, { licence: 125 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad language', async function () {
const fields = immutableAssign(baseCorrectParams, { language: 125 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without nsfw attribute', async function () {
const fields = omit(baseCorrectParams, 'nsfw')
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad nsfw attribute', async function () {
const fields = immutableAssign(baseCorrectParams, { nsfw: 2 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without commentsEnabled attribute', async function () {
const fields = omit(baseCorrectParams, 'commentsEnabled')
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad commentsEnabled attribute', async function () {
const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a long description', async function () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without a channel', async function () {
const fields = omit(baseCorrectParams, 'channelId')
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad channel', async function () {
const fields = immutableAssign(baseCorrectParams, { channelId: 545454 })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with another user channel', async function () {
const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with too many tags', async function () {
const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a tag length too low', async function () {
const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a tag length too big', async function () {
const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] })
const attaches = baseCorrectAttaches
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without an input file', async function () {
const fields = baseCorrectParams
const attaches = {}
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail without an incorrect input file', async function () {
const attaches = {
'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
}
- await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with an incorrect thumbnail file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
+ 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+ }
+
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with a big thumbnail file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
+ 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+ }
+
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with an incorrect preview file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
+ 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+ }
+
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with a big preview file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
+ 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+ }
+
+ await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should succeed with the correct parameters', async function () {
{
const attaches = baseCorrectAttaches
- await makePostUploadRequest({
+ await makeUploadRequest({
url: server.url,
path: path + '/upload',
token: server.accessToken,
videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4')
})
- await makePostUploadRequest({
+ await makeUploadRequest({
url: server.url,
path: path + '/upload',
token: server.accessToken,
videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv')
})
- await makePostUploadRequest({
+ await makeUploadRequest({
url: server.url,
path: path + '/upload',
token: server.accessToken,
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
})
+ it('Should fail with an incorrect thumbnail file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png')
+ }
+
+ await makeUploadRequest({
+ url: server.url,
+ method: 'PUT',
+ path: path + videoId,
+ token: server.accessToken,
+ fields,
+ attaches
+ })
+ })
+
+ it('Should fail with a big thumbnail file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
+ }
+
+ await makeUploadRequest({
+ url: server.url,
+ method: 'PUT',
+ path: path + videoId,
+ token: server.accessToken,
+ fields,
+ attaches
+ })
+ })
+
+ it('Should fail with an incorrect preview file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png')
+ }
+
+ await makeUploadRequest({
+ url: server.url,
+ method: 'PUT',
+ path: path + videoId,
+ token: server.accessToken,
+ fields,
+ attaches
+ })
+ })
+
+ it('Should fail with a big preview file', async function () {
+ const fields = baseCorrectParams
+ const attaches = {
+ 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
+ }
+
+ await makeUploadRequest({
+ url: server.url,
+ method: 'PUT',
+ path: path + videoId,
+ token: server.accessToken,
+ fields,
+ attaches
+ })
+ })
+
it('Should fail with a video of another user')
it('Should fail with a video of another server')
nsfw: true,
description: 'my super description for server 2',
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
- fixture: 'video_short2.webm'
+ fixture: 'video_short2.webm',
+ thumbnailfile: 'thumbnail.jpg',
+ previewfile: 'preview.jpg'
}
await uploadVideo(servers[1].url, userAccessToken, videoAttributes)
resolution: 720,
size: 710000
}
- ]
+ ],
+ thumbnailfile: 'thumbnail',
+ previewfile: 'preview'
}
const res = await getVideosList(server.url)
language: 13,
nsfw: true,
description: 'my super description updated',
- tags: [ 'tag_up_1', 'tag_up_2' ]
+ tags: [ 'tag_up_1', 'tag_up_2' ],
+ thumbnailfile: 'thumbnail.jpg',
+ previewfile: 'preview.jpg'
}
await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes)
resolution: 720,
size: 292677
}
- ]
+ ],
+ thumbnailfile: 'thumbnail',
+ previewfile: 'preview'
}
await completeVideoCheck(server.url, videoUpdated, checkAttributes)
}
/* tslint:disable:no-unused-expression */
-import { join } from 'path'
+import { isAbsolute, join } from 'path'
import * as request from 'supertest'
import * as WebTorrent from 'webtorrent'
import { readFileBufferPromise } from '../../../helpers/core-utils'
const body = res.body
const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension))
- const minLength = body.length - ((50 * body.length) / 100)
- const maxLength = body.length + ((50 * body.length) / 100)
+ const minLength = body.length - ((20 * body.length) / 100)
+ const maxLength = body.length + ((20 * body.length) / 100)
return data.length > minLength && data.length < maxLength
} else {
}
}
+function buildAbsoluteFixturePath (path: string) {
+ if (isAbsolute(path)) {
+ return path
+ }
+
+ return join(__dirname, '..', '..', 'api', 'fixtures', path)
+}
+
// ---------------------------------------------------------------------------
export {
webtorrentAdd,
immutableAssign,
testImage,
+ buildAbsoluteFixturePath,
root
}
import * as request from 'supertest'
+import { buildAbsoluteFixturePath } from '../'
function makeGetRequest (options: {
url: string,
.expect(options.statusCodeExpected)
}
-function makePostUploadRequest (options: {
+function makeUploadRequest (options: {
url: string,
+ method?: 'POST' | 'PUT',
path: string,
token: string,
fields: { [ fieldName: string ]: any },
}) {
if (!options.statusCodeExpected) options.statusCodeExpected = 400
- const req = request(options.url)
- .post(options.path)
- .set('Accept', 'application/json')
+ let req: request.Test
+ if (options.method === 'PUT') {
+ req = request(options.url).put(options.path)
+ } else {
+ req = request(options.url).post(options.path)
+ }
+
+ req.set('Accept', 'application/json')
if (options.token) req.set('Authorization', 'Bearer ' + options.token)
Object.keys(options.attaches).forEach(attach => {
const value = options.attaches[attach]
- req.attach(attach, value)
+ req.attach(attach, buildAbsoluteFixturePath(value))
})
return req.expect(options.statusCodeExpected)
export {
makeGetRequest,
- makePostUploadRequest,
+ makeUploadRequest,
makePostBodyRequest,
makePutBodyRequest,
makeDeleteRequest
import { isAbsolute, join } from 'path'
import * as request from 'supertest'
-import { makePostBodyRequest, makePostUploadRequest, makePutBodyRequest } from '../'
+import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
import { UserRole } from '../../../../shared/index'
filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture)
}
- return makePostUploadRequest({
+ return makeUploadRequest({
url: options.url,
path,
token: options.accessToken,
import * as parseTorrent from 'parse-torrent'
import { extname, isAbsolute, join } from 'path'
import * as request from 'supertest'
-import { getMyUserInformation, makeGetRequest, root, ServerInfo, testImage } from '../'
+import {
+ buildAbsoluteFixturePath,
+ getMyUserInformation,
+ makeGetRequest,
+ makePutBodyRequest,
+ makeUploadRequest,
+ root,
+ ServerInfo,
+ testImage
+} from '../'
import { VideoPrivacy } from '../../../../shared/models/videos'
import { readdirPromise } from '../../../helpers/core-utils'
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
channelId?: number
privacy?: VideoPrivacy
fixture?: string
+ thumbnailfile?: string
+ previewfile?: string
}
function getVideoCategories (url: string) {
defaultChannelId = res.body.videoChannels[0].id
} catch (e) { /* empty */ }
- // Default attributes
- let attributes = {
+ // Override default attributes
+ const attributes = Object.assign({
name: 'my super video',
category: 5,
licence: 4,
privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
fixture: 'video_short.webm'
- }
- attributes = Object.assign(attributes, videoAttributesArg)
+ }, videoAttributesArg)
const req = request(url)
.post(path)
req.field('licence', attributes.licence.toString())
}
- for (let i = 0; i < attributes.tags.length; i++) {
- req.field('tags[' + i + ']', attributes.tags[i])
+ if (attributes.thumbnailfile !== undefined) {
+ req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
+ }
+ if (attributes.previewfile !== undefined) {
+ req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
}
- let filePath = ''
- if (isAbsolute(attributes.fixture)) {
- filePath = attributes.fixture
- } else {
- filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture)
+ for (let i = 0; i < attributes.tags.length; i++) {
+ req.field('tags[' + i + ']', attributes.tags[i])
}
- return req.attach('videofile', filePath)
+ return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
.expect(specialStatus)
}
-function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) {
+function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
const path = '/api/v1/videos/' + id
const body = {}
if (attributes.tags) body['tags'] = attributes.tags
if (attributes.privacy) body['privacy'] = attributes.privacy
- return request(url)
- .put(path)
- .send(body)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + accessToken)
- .expect(specialStatus)
+ // Upload request
+ if (attributes.thumbnailfile || attributes.previewfile) {
+ const attaches: any = {}
+ if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
+ if (attributes.previewfile) attaches.previewfile = attributes.previewfile
+
+ return makeUploadRequest({
+ url,
+ method: 'PUT',
+ path,
+ token: accessToken,
+ fields: body,
+ attaches,
+ statusCodeExpected
+ })
+ }
+
+ return makePutBodyRequest({
+ url,
+ path,
+ fields: body,
+ token: accessToken,
+ statusCodeExpected
+ })
}
function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
files: {
resolution: number
size: number
- }[]
+ }[],
+ thumbnailfile?: string
+ previewfile?: string
}
) {
if (!attributes.likes) attributes.likes = 0
const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
expect(file.size).to.be.above(minSize).and.below(maxSize)
- const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath)
- expect(test).to.equal(true)
+ {
+ const test = await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
+ expect(test).to.equal(true)
+ }
+
+ if (attributes.previewfile) {
+ const test = await testImage(url, attributes.previewfile, videoDetails.previewPath)
+ expect(test).to.equal(true)
+ }
const torrent = await webtorrentAdd(magnetUri, true)
expect(torrent.files).to.be.an('array')
import * as program from 'commander'
-import { createWriteStream } from 'fs'
import { join } from 'path'
-import { cursorTo } from 'readline'
import * as youtubeDL from 'youtube-dl'
import { VideoPrivacy } from '../../shared/models/videos'
import { unlinkPromise } from '../helpers/core-utils'