import * as express from 'express'
import 'multer'
-import { extname, join } from 'path'
-import * as uuidv4 from 'uuid/v4'
import * as RateLimit from 'express-rate-limit'
import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
-import { processImage } from '../../helpers/image-utils'
import { logger } from '../../helpers/logger'
import { getFormattedObjects } from '../../helpers/utils'
-import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
-import { updateActorAvatarInstance } from '../../lib/activitypub'
+import { CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
import { sendUpdateActor } from '../../lib/activitypub/send'
import { Emailer } from '../../lib/emailer'
import { Redis } from '../../lib/redis'
usersUpdateValidator,
usersVideoRatingValidator
} from '../../middlewares'
-import {
- usersAskResetPasswordValidator,
- usersResetPasswordValidator,
- usersUpdateMyAvatarValidator,
- videosSortValidator
-} from '../../middlewares/validators'
+import { usersAskResetPasswordValidator, usersResetPasswordValidator, videosSortValidator } from '../../middlewares/validators'
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
import { UserModel } from '../../models/account/user'
import { OAuthTokenModel } from '../../models/oauth/oauth-token'
import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
import { createReqFiles } from '../../helpers/express-utils'
import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model'
+import { updateAvatarValidator } from '../../middlewares/validators/avatar'
+import { updateActorAvatarFile } from '../../lib/avatar'
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const loginRateLimiter = new RateLimit({
usersRouter.post('/me/avatar/pick',
authenticate,
reqAvatarFile,
- usersUpdateMyAvatarValidator,
+ updateAvatarValidator,
asyncMiddleware(updateMyAvatar)
)
async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
- const user = res.locals.oauth.token.user
- const actor = user.Account.Actor
-
- const extension = extname(avatarPhysicalFile.filename)
- const avatarName = uuidv4() + extension
- 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)
- await updatedActor.save({ transaction: t })
+ const account = res.locals.oauth.token.user.Account
- await sendUpdateActor(user.Account, t)
-
- return updatedActor.Avatar
- })
+ const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account)
return res
.json({
import { sendUpdateActor } from '../../lib/activitypub/send'
import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
import { createVideoChannel } from '../../lib/video-channel'
-import { isNSFWHidden } from '../../helpers/express-utils'
+import { createReqFiles, isNSFWHidden } from '../../helpers/express-utils'
import { setAsyncActorKeys } from '../../lib/activitypub'
import { AccountModel } from '../../models/account/account'
-import { sequelizeTypescript } from '../../initializers'
+import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
import { logger } from '../../helpers/logger'
import { VideoModel } from '../../models/video/video'
+import { updateAvatarValidator } from '../../middlewares/validators/avatar'
+import { updateActorAvatarFile } from '../../lib/avatar'
+
+const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const videoChannelRouter = express.Router()
asyncRetryTransactionMiddleware(addVideoChannel)
)
+videoChannelRouter.post('/:id/avatar/pick',
+ authenticate,
+ reqAvatarFile,
+ // Check the rights
+ asyncMiddleware(videoChannelsUpdateValidator),
+ updateAvatarValidator,
+ asyncMiddleware(updateVideoChannelAvatar)
+)
+
videoChannelRouter.put('/:id',
authenticate,
asyncMiddleware(videoChannelsUpdateValidator),
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
+async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
+ const videoChannel = res.locals.videoChannel
+
+ const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel)
+
+ return res
+ .json({
+ avatar: avatar.toFormattedJSON()
+ })
+ .end()
+}
+
async function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body
const account: AccountModel = res.locals.oauth.token.User.Account
--- /dev/null
+import 'multer'
+import * as uuidv4 from 'uuid'
+import { sendUpdateActor } from './activitypub/send'
+import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers'
+import { updateActorAvatarInstance } from './activitypub'
+import { processImage } from '../helpers/image-utils'
+import { ActorModel } from '../models/activitypub/actor'
+import { AccountModel } from '../models/account/account'
+import { VideoChannelModel } from '../models/video/video-channel'
+import { extname, join } from 'path'
+
+async function updateActorAvatarFile (
+ avatarPhysicalFile: Express.Multer.File,
+ actor: ActorModel,
+ accountOrChannel: AccountModel | VideoChannelModel
+) {
+ const extension = extname(avatarPhysicalFile.filename)
+ const avatarName = uuidv4() + extension
+ const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
+ await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
+
+ return sequelizeTypescript.transaction(async t => {
+ const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
+ await updatedActor.save({ transaction: t })
+
+ await sendUpdateActor(accountOrChannel, t)
+
+ return updatedActor.Avatar
+ })
+}
+
+export {
+ updateActorAvatarFile
+}
--- /dev/null
+import * as express from 'express'
+import { body } from 'express-validator/check'
+import { isAvatarFile } from '../../helpers/custom-validators/users'
+import { areValidationErrors } from './utils'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
+import { logger } from '../../helpers/logger'
+
+const updateAvatarValidator = [
+ body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
+ 'This file is not supported or too large. Please, make sure it is of the following type : '
+ + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
+ ),
+
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking updateAvatarValidator parameters', { files: req.files })
+
+ if (areValidationErrors(req, res)) return
+
+ return next()
+ }
+]
+
+export {
+ updateAvatarValidator
+}
import { omit } from 'lodash'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import {
- isAvatarFile,
isUserAutoPlayVideoValid,
- isUserDescriptionValid, isUserDisplayNameValid,
+ isUserDescriptionValid,
+ isUserDisplayNameValid,
isUserNSFWPolicyValid,
isUserPasswordValid,
isUserRoleValid,
import { isVideoExist } from '../../helpers/custom-validators/videos'
import { logger } from '../../helpers/logger'
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/utils'
-import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
import { Redis } from '../../lib/redis'
import { UserModel } from '../../models/account/user'
import { areValidationErrors } from './utils'
}
]
-const usersUpdateMyAvatarValidator = [
- body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
- 'This file is not supported or too large. Please, make sure it is of the following type : '
- + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
- ),
-
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking usersUpdateMyAvatarValidator parameters', { files: req.files })
-
- if (areValidationErrors(req, res)) return
-
- return next()
- }
-]
-
const usersGetValidator = [
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
ensureUserRegistrationAllowed,
ensureUserRegistrationAllowedForIP,
usersGetValidator,
- usersUpdateMyAvatarValidator,
usersAskResetPasswordValidator,
usersResetPasswordValidator
}
import { UserModel } from '../../models/account/user'
import { VideoChannelModel } from '../../models/video/video-channel'
import { areValidationErrors } from './utils'
+import { isAvatarFile } from '../../helpers/custom-validators/users'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
const listVideoAccountChannelsValidator = [
param('accountName').exists().withMessage('Should have a valid account name'),
await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
})
+ it('Should fail with an unauthenticated user', async function () {
+ const fields = {}
+ const attaches = {
+ 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png')
+ }
+ await makeUploadRequest({
+ url: server.url,
+ path: path + '/me/avatar/pick',
+ fields,
+ attaches,
+ statusCodeExpected: 401
+ })
+ })
+
it('Should succeed with the correct params', async function () {
const fields = {}
const attaches = {
killallServers,
makeGetRequest,
makePostBodyRequest,
- makePutBodyRequest,
+ makePutBodyRequest, makeUploadRequest,
runServer,
ServerInfo,
setAccessTokensToServers,
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
import { User } from '../../../../shared/models/users'
+import { join } from "path"
const expect = chai.expect
})
})
+ describe('When updating video channel avatar', function () {
+ let path: string
+
+ before(async function () {
+ path = videoChannelPath + '/' + videoChannelUUID
+ })
+
+ it('Should fail with an incorrect input file', async function () {
+ const fields = {}
+ const attaches = {
+ 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
+ }
+ await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with a big file', async function () {
+ const fields = {}
+ const attaches = {
+ 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
+ }
+ await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
+ })
+
+ it('Should fail with an unauthenticated user', async function () {
+ const fields = {}
+ const attaches = {
+ 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png')
+ }
+ await makeUploadRequest({
+ url: server.url,
+ path: path + '/avatar/pick',
+ fields,
+ attaches,
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ const fields = {}
+ const attaches = {
+ 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png')
+ }
+ await makeUploadRequest({
+ url: server.url,
+ path: path + '/avatar/pick',
+ token: server.accessToken,
+ fields,
+ attaches,
+ statusCodeExpected: 200
+ })
+ })
+ })
+
describe('When getting a video channel', function () {
it('Should return the list of the video channels with nothing', async function () {
const res = await makeGetRequest({
import * as chai from 'chai'
import 'mocha'
import { User, Video } from '../../../../shared/index'
-import { doubleFollow, flushAndRunMultipleServers, getVideoChannelVideos, updateVideo, uploadVideo } from '../../utils'
+import {
+ doubleFollow,
+ flushAndRunMultipleServers,
+ getVideoChannelVideos, testImage,
+ updateVideo,
+ updateVideoChannelAvatar,
+ uploadVideo, wait
+} from '../../utils'
import {
addVideoChannel,
deleteVideoChannel,
}
})
+ it('Should update video channel avatar', async function () {
+ this.timeout(5000)
+
+ const fixture = 'avatar.png'
+
+ await updateVideoChannelAvatar({
+ url: servers[0].url,
+ accessToken: servers[0].accessToken,
+ videoChannelId: secondVideoChannelId,
+ fixture
+ })
+
+ await waitJobs(servers)
+ })
+
+ it('Should have video channel avatar updated', async function () {
+ for (const server of servers) {
+ const res = await getVideoChannelsList(server.url, 0, 1, '-name')
+
+ const videoChannel = res.body.data.find(c => c.id === secondVideoChannelId)
+
+ await testImage(server.url, 'avatar-resized', videoChannel.avatar.path, '.png')
+ }
+ })
+
it('Should get video channel', async function () {
const res = await getVideoChannel(servers[0].url, secondVideoChannelId)
import * as request from 'supertest'
import { buildAbsoluteFixturePath } from '../miscs/miscs'
+import { isAbsolute, join } from 'path'
function makeGetRequest (options: {
url: string,
url: string,
method?: 'POST' | 'PUT',
path: string,
- token: string,
+ token?: string,
fields: { [ fieldName: string ]: any },
attaches: { [ attachName: string ]: any },
statusCodeExpected?: number
.expect(options.statusCodeExpected)
}
+function updateAvatarRequest (options: {
+ url: string,
+ path: string,
+ accessToken: string,
+ fixture: string
+}) {
+ let filePath = ''
+ if (isAbsolute(options.fixture)) {
+ filePath = options.fixture
+ } else {
+ filePath = join(__dirname, '..', '..', 'fixtures', options.fixture)
+ }
+
+ return makeUploadRequest({
+ url: options.url,
+ path: options.path,
+ token: options.accessToken,
+ fields: {},
+ attaches: { avatarfile: filePath },
+ statusCodeExpected: 200
+ })
+}
+
// ---------------------------------------------------------------------------
export {
makeUploadRequest,
makePostBodyRequest,
makePutBodyRequest,
- makeDeleteRequest
+ makeDeleteRequest,
+ updateAvatarRequest
}
-import { isAbsolute, join } from 'path'
import * as request from 'supertest'
-import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
+import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../'
import { UserRole } from '../../../../shared/index'
import { NSFWPolicyType } from '../../../../shared/models/videos/nsfw-policy.type'
fixture: string
}) {
const path = '/api/v1/users/me/avatar/pick'
- let filePath = ''
- if (isAbsolute(options.fixture)) {
- filePath = options.fixture
- } else {
- filePath = join(__dirname, '..', '..', 'fixtures', options.fixture)
- }
- return makeUploadRequest({
- url: options.url,
- path,
- token: options.accessToken,
- fields: {},
- attaches: { avatarfile: filePath },
- statusCodeExpected: 200
- })
+ return updateAvatarRequest(Object.assign(options, { path }))
}
function updateUser (options: {
import * as request from 'supertest'
import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared/models/videos'
+import { updateAvatarRequest } from '../index'
function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
const path = '/api/v1/video-channels'
.expect('Content-Type', /json/)
}
+function updateVideoChannelAvatar (options: {
+ url: string,
+ accessToken: string,
+ fixture: string,
+ videoChannelId: string | number
+}) {
+
+ const path = '/api/v1/video-channels/' + options.videoChannelId + '/avatar/pick'
+
+ return updateAvatarRequest(Object.assign(options, { path }))
+}
+
// ---------------------------------------------------------------------------
export {
+ updateVideoChannelAvatar,
getVideoChannelsList,
getAccountVideoChannelsList,
addVideoChannel,