aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/users/me.ts2
-rw-r--r--server/controllers/api/video-channel.ts2
-rw-r--r--server/helpers/custom-validators/actor-images.ts17
-rw-r--r--server/helpers/custom-validators/users.ts13
-rw-r--r--server/lib/activitypub/actor.ts72
-rw-r--r--server/lib/activitypub/process/process-update.ts9
-rw-r--r--server/lib/actor-image.ts3
-rw-r--r--server/middlewares/validators/actor-image.ts (renamed from server/middlewares/validators/avatar.ts)10
-rw-r--r--server/middlewares/validators/index.ts1
-rw-r--r--server/models/video/video-channel.ts9
-rw-r--r--server/tests/api/check-params/video-channels.ts70
-rw-r--r--server/tests/api/videos/video-channels.ts86
-rw-r--r--server/tests/fixtures/banner-resized.jpgbin0 -> 88780 bytes
-rw-r--r--server/tests/fixtures/banner.jpgbin0 -> 31648 bytes
-rw-r--r--server/typings/express/index.d.ts1
-rw-r--r--shared/extra-utils/requests/requests.ts7
-rw-r--r--shared/extra-utils/users/users.ts4
-rw-r--r--shared/extra-utils/videos/video-channels.ts29
18 files changed, 210 insertions, 125 deletions
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 25a18caa5..9f9d2d77f 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -25,7 +25,7 @@ import {
25 usersVideoRatingValidator 25 usersVideoRatingValidator
26} from '../../../middlewares' 26} from '../../../middlewares'
27import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators' 27import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators'
28import { updateAvatarValidator } from '../../../middlewares/validators/avatar' 28import { updateAvatarValidator } from '../../../middlewares/validators/actor-image'
29import { AccountModel } from '../../../models/account/account' 29import { AccountModel } from '../../../models/account/account'
30import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 30import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
31import { UserModel } from '../../../models/account/user' 31import { UserModel } from '../../../models/account/user'
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 1c926722d..149d6cfb4 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -33,7 +33,7 @@ import {
33 videoPlaylistsSortValidator 33 videoPlaylistsSortValidator
34} from '../../middlewares' 34} from '../../middlewares'
35import { videoChannelsNameWithHostValidator, videoChannelsOwnSearchValidator, videosSortValidator } from '../../middlewares/validators' 35import { videoChannelsNameWithHostValidator, videoChannelsOwnSearchValidator, videosSortValidator } from '../../middlewares/validators'
36import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/avatar' 36import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image'
37import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' 37import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
38import { AccountModel } from '../../models/account/account' 38import { AccountModel } from '../../models/account/account'
39import { VideoModel } from '../../models/video/video' 39import { VideoModel } from '../../models/video/video'
diff --git a/server/helpers/custom-validators/actor-images.ts b/server/helpers/custom-validators/actor-images.ts
new file mode 100644
index 000000000..4fb0b7c70
--- /dev/null
+++ b/server/helpers/custom-validators/actor-images.ts
@@ -0,0 +1,17 @@
1
2import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
3import { isFileValid } from './misc'
4
5const imageMimeTypes = CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
6 .map(v => v.replace('.', ''))
7 .join('|')
8const imageMimeTypesRegex = `image/(${imageMimeTypes})`
9function isActorImageFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], fieldname: string) {
10 return isFileValid(files, imageMimeTypesRegex, fieldname, CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max)
11}
12
13// ---------------------------------------------------------------------------
14
15export {
16 isActorImageFile
17}
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index 85f3634c8..5b21c3529 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -3,7 +3,7 @@ import validator from 'validator'
3import { UserRole } from '../../../shared' 3import { UserRole } from '../../../shared'
4import { isEmailEnabled } from '../../initializers/config' 4import { isEmailEnabled } from '../../initializers/config'
5import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' 5import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
6import { exists, isArray, isBooleanValid, isFileValid } from './misc' 6import { exists, isArray, isBooleanValid } from './misc'
7 7
8const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS 8const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
9 9
@@ -97,14 +97,6 @@ function isUserRoleValid (value: any) {
97 return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined 97 return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
98} 98}
99 99
100const avatarMimeTypes = CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
101 .map(v => v.replace('.', ''))
102 .join('|')
103const avatarMimeTypesRegex = `image/(${avatarMimeTypes})`
104function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
105 return isFileValid(files, avatarMimeTypesRegex, 'avatarfile', CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max)
106}
107
108// --------------------------------------------------------------------------- 100// ---------------------------------------------------------------------------
109 101
110export { 102export {
@@ -128,6 +120,5 @@ export {
128 isUserDisplayNameValid, 120 isUserDisplayNameValid,
129 isUserDescriptionValid, 121 isUserDescriptionValid,
130 isNoInstanceConfigWarningModal, 122 isNoInstanceConfigWarningModal,
131 isNoWelcomeModal, 123 isNoWelcomeModal
132 isAvatarFile
133} 124}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index fe4796a3d..917fed6ec 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -34,6 +34,7 @@ import {
34 MActorFull, 34 MActorFull,
35 MActorFullActor, 35 MActorFullActor,
36 MActorId, 36 MActorId,
37 MActorImage,
37 MActorImages, 38 MActorImages,
38 MChannel 39 MChannel
39} from '../../types/models' 40} from '../../types/models'
@@ -169,38 +170,34 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
169 } 170 }
170} 171}
171 172
172type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string, type: ActorImageType } 173type ImageInfo = { name: string, onDisk?: boolean, fileUrl: string }
173async function updateActorImageInstance (actor: MActorImages, info: AvatarInfo, t: Transaction) { 174async function updateActorImageInstance (actor: MActorImages, type: ActorImageType, imageInfo: ImageInfo | null, t: Transaction) {
174 if (!info.name) return actor 175 const oldImageModel = type === ActorImageType.AVATAR
175
176 const oldImageModel = info.type === ActorImageType.AVATAR
177 ? actor.Avatar 176 ? actor.Avatar
178 : actor.Banner 177 : actor.Banner
179 178
180 if (oldImageModel) { 179 if (oldImageModel) {
181 // Don't update the avatar if the file URL did not change 180 // Don't update the avatar if the file URL did not change
182 if (info.fileUrl && oldImageModel.fileUrl === info.fileUrl) return actor 181 if (imageInfo?.fileUrl && oldImageModel.fileUrl === imageInfo.fileUrl) return actor
183 182
184 try { 183 try {
185 await oldImageModel.destroy({ transaction: t }) 184 await oldImageModel.destroy({ transaction: t })
185
186 setActorImage(actor, type, null)
186 } catch (err) { 187 } catch (err) {
187 logger.error('Cannot remove old actor image of actor %s.', actor.url, { err }) 188 logger.error('Cannot remove old actor image of actor %s.', actor.url, { err })
188 } 189 }
189 } 190 }
190 191
191 const imageModel = await ActorImageModel.create({ 192 if (imageInfo) {
192 filename: info.name, 193 const imageModel = await ActorImageModel.create({
193 onDisk: info.onDisk, 194 filename: imageInfo.name,
194 fileUrl: info.fileUrl, 195 onDisk: imageInfo.onDisk ?? false,
195 type: info.type 196 fileUrl: imageInfo.fileUrl,
196 }, { transaction: t }) 197 type: type
198 }, { transaction: t })
197 199
198 if (info.type === ActorImageType.AVATAR) { 200 setActorImage(actor, type, imageModel)
199 actor.avatarId = imageModel.id
200 actor.Avatar = imageModel
201 } else {
202 actor.bannerId = imageModel.id
203 actor.Banner = imageModel
204 } 201 }
205 202
206 return actor 203 return actor
@@ -310,27 +307,8 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel
310 return sequelizeTypescript.transaction(async t => { 307 return sequelizeTypescript.transaction(async t => {
311 updateInstanceWithAnother(actor, result.actor) 308 updateInstanceWithAnother(actor, result.actor)
312 309
313 if (result.avatar !== undefined) { 310 await updateActorImageInstance(actor, ActorImageType.AVATAR, result.avatar, t)
314 const avatarInfo = { 311 await updateActorImageInstance(actor, ActorImageType.BANNER, result.banner, t)
315 name: result.avatar.name,
316 fileUrl: result.avatar.fileUrl,
317 onDisk: false,
318 type: ActorImageType.AVATAR
319 }
320
321 await updateActorImageInstance(actor, avatarInfo, t)
322 }
323
324 if (result.banner !== undefined) {
325 const bannerInfo = {
326 name: result.banner.name,
327 fileUrl: result.banner.fileUrl,
328 onDisk: false,
329 type: ActorImageType.BANNER
330 }
331
332 await updateActorImageInstance(actor, bannerInfo, t)
333 }
334 312
335 // Force update 313 // Force update
336 actor.setDataValue('updatedAt', new Date()) 314 actor.setDataValue('updatedAt', new Date())
@@ -381,6 +359,22 @@ export {
381 359
382// --------------------------------------------------------------------------- 360// ---------------------------------------------------------------------------
383 361
362function setActorImage (actorModel: MActorImages, type: ActorImageType, imageModel: MActorImage) {
363 const id = imageModel
364 ? imageModel.id
365 : null
366
367 if (type === ActorImageType.AVATAR) {
368 actorModel.avatarId = id
369 actorModel.Avatar = imageModel
370 } else {
371 actorModel.bannerId = id
372 actorModel.Banner = imageModel
373 }
374
375 return actorModel
376}
377
384function saveActorAndServerAndModelIfNotExist ( 378function saveActorAndServerAndModelIfNotExist (
385 result: FetchRemoteActorResult, 379 result: FetchRemoteActorResult,
386 ownerActor?: MActorFullActor, 380 ownerActor?: MActorFullActor,
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index ad3bb392d..6df9b93b2 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -134,13 +134,8 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate)
134 134
135 await updateActorInstance(actor, actorAttributesToUpdate) 135 await updateActorInstance(actor, actorAttributesToUpdate)
136 136
137 for (const imageInfo of [ avatarInfo, bannerInfo ]) { 137 await updateActorImageInstance(actor, ActorImageType.AVATAR, avatarInfo, t)
138 if (!imageInfo) continue 138 await updateActorImageInstance(actor, ActorImageType.BANNER, bannerInfo, t)
139
140 const imageOptions = Object.assign({}, imageInfo, { onDisk: false })
141
142 await updateActorImageInstance(actor, imageOptions, t)
143 }
144 139
145 await actor.save({ transaction: t }) 140 await actor.save({ transaction: t })
146 141
diff --git a/server/lib/actor-image.ts b/server/lib/actor-image.ts
index 59afa93bd..fa1a2a18a 100644
--- a/server/lib/actor-image.ts
+++ b/server/lib/actor-image.ts
@@ -34,11 +34,10 @@ async function updateLocalActorImageFile (
34 const actorImageInfo = { 34 const actorImageInfo = {
35 name: imageName, 35 name: imageName,
36 fileUrl: null, 36 fileUrl: null,
37 type,
38 onDisk: true 37 onDisk: true
39 } 38 }
40 39
41 const updatedActor = await updateActorImageInstance(accountOrChannel.Actor, actorImageInfo, t) 40 const updatedActor = await updateActorImageInstance(accountOrChannel.Actor, type, actorImageInfo, t)
42 await updatedActor.save({ transaction: t }) 41 await updatedActor.save({ transaction: t })
43 42
44 await sendUpdateActor(accountOrChannel, t) 43 await sendUpdateActor(accountOrChannel, t)
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/actor-image.ts
index f7eb367bd..961d7a7e5 100644
--- a/server/middlewares/validators/avatar.ts
+++ b/server/middlewares/validators/actor-image.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body } from 'express-validator' 2import { body } from 'express-validator'
3import { isAvatarFile } from '../../helpers/custom-validators/users' 3import { isActorImageFile } from '@server/helpers/custom-validators/actor-images'
4import { areValidationErrors } from './utils'
5import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
6import { logger } from '../../helpers/logger'
7import { cleanUpReqFiles } from '../../helpers/express-utils' 4import { cleanUpReqFiles } from '../../helpers/express-utils'
5import { logger } from '../../helpers/logger'
6import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
7import { areValidationErrors } from './utils'
8 8
9const updateActorImageValidatorFactory = (fieldname: string) => ([ 9const updateActorImageValidatorFactory = (fieldname: string) => ([
10 body(fieldname).custom((value, { req }) => isAvatarFile(req.files)).withMessage( 10 body(fieldname).custom((value, { req }) => isActorImageFile(req.files, fieldname)).withMessage(
11 'This file is not supported or too large. Please, make sure it is of the following type : ' + 11 'This file is not supported or too large. Please, make sure it is of the following type : ' +
12 CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME.join(', ') 12 CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME.join(', ')
13 ), 13 ),
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 4086d77aa..24faeea3e 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -1,5 +1,6 @@
1export * from './abuse' 1export * from './abuse'
2export * from './account' 2export * from './account'
3export * from './actor-image'
3export * from './blocklist' 4export * from './blocklist'
4export * from './oembed' 5export * from './oembed'
5export * from './activitypub' 6export * from './activitypub'
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 74885edfb..d2a055f5b 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -99,7 +99,14 @@ export type SummaryOptions = {
99 } 99 }
100 } 100 }
101 ] 101 ]
102 } 102 },
103 include: [
104 {
105 model: ActorImageModel,
106 as: 'Banner',
107 required: false
108 }
109 ]
103 }, 110 },
104 { 111 {
105 model: AccountModel, 112 model: AccountModel,
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index 0dd436426..bc2e6192e 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -234,7 +234,8 @@ describe('Test video channels API validator', function () {
234 }) 234 })
235 }) 235 })
236 236
237 describe('When updating video channel avatar', function () { 237 describe('When updating video channel avatar/banner', function () {
238 const types = [ 'avatar', 'banner' ]
238 let path: string 239 let path: string
239 240
240 before(async function () { 241 before(async function () {
@@ -242,48 +243,57 @@ describe('Test video channels API validator', function () {
242 }) 243 })
243 244
244 it('Should fail with an incorrect input file', async function () { 245 it('Should fail with an incorrect input file', async function () {
245 const fields = {} 246 for (const type of types) {
246 const attaches = { 247 const fields = {}
247 avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') 248 const attaches = {
249 [type + 'file']: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
250 }
251
252 await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
248 } 253 }
249 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
250 }) 254 })
251 255
252 it('Should fail with a big file', async function () { 256 it('Should fail with a big file', async function () {
253 const fields = {} 257 for (const type of types) {
254 const attaches = { 258 const fields = {}
255 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 259 const attaches = {
260 [type + 'file']: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
261 }
262 await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
256 } 263 }
257 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
258 }) 264 })
259 265
260 it('Should fail with an unauthenticated user', async function () { 266 it('Should fail with an unauthenticated user', async function () {
261 const fields = {} 267 for (const type of types) {
262 const attaches = { 268 const fields = {}
263 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') 269 const attaches = {
270 [type + 'file']: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
271 }
272 await makeUploadRequest({
273 url: server.url,
274 path: `${path}/${type}/pick`,
275 fields,
276 attaches,
277 statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401
278 })
264 } 279 }
265 await makeUploadRequest({
266 url: server.url,
267 path: path + '/avatar/pick',
268 fields,
269 attaches,
270 statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401
271 })
272 }) 280 })
273 281
274 it('Should succeed with the correct params', async function () { 282 it('Should succeed with the correct params', async function () {
275 const fields = {} 283 for (const type of types) {
276 const attaches = { 284 const fields = {}
277 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') 285 const attaches = {
286 [type + 'file']: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
287 }
288 await makeUploadRequest({
289 url: server.url,
290 path: `${path}/${type}/pick`,
291 token: server.accessToken,
292 fields,
293 attaches,
294 statusCodeExpected: HttpStatusCode.OK_200
295 })
278 } 296 }
279 await makeUploadRequest({
280 url: server.url,
281 path: path + '/avatar/pick',
282 token: server.accessToken,
283 fields,
284 attaches,
285 statusCodeExpected: HttpStatusCode.OK_200
286 })
287 }) 297 })
288 }) 298 })
289 299
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index 367f99fdd..8033b9ba5 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -5,13 +5,14 @@ import * as chai from 'chai'
5import { 5import {
6 cleanupTests, 6 cleanupTests,
7 createUser, 7 createUser,
8 deleteVideoChannelImage,
8 doubleFollow, 9 doubleFollow,
9 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
10 getVideo, 11 getVideo,
11 getVideoChannelVideos, 12 getVideoChannelVideos,
12 testImage, 13 testImage,
13 updateVideo, 14 updateVideo,
14 updateVideoChannelAvatar, 15 updateVideoChannelImage,
15 uploadVideo, 16 uploadVideo,
16 userLogin, 17 userLogin,
17 wait 18 wait
@@ -21,7 +22,6 @@ import {
21 deleteVideoChannel, 22 deleteVideoChannel,
22 getAccountVideoChannelsList, 23 getAccountVideoChannelsList,
23 getMyUserInformation, 24 getMyUserInformation,
24 getVideoChannel,
25 getVideoChannelsList, 25 getVideoChannelsList,
26 ServerInfo, 26 ServerInfo,
27 setAccessTokensToServers, 27 setAccessTokensToServers,
@@ -33,6 +33,13 @@ import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/inde
33 33
34const expect = chai.expect 34const expect = chai.expect
35 35
36async function findChannel (server: ServerInfo, channelId: number) {
37 const res = await getVideoChannelsList(server.url, 0, 5, '-name')
38 const videoChannel = res.body.data.find(c => c.id === channelId)
39
40 return videoChannel as VideoChannel
41}
42
36describe('Test video channels', function () { 43describe('Test video channels', function () {
37 let servers: ServerInfo[] 44 let servers: ServerInfo[]
38 let userInfo: User 45 let userInfo: User
@@ -262,38 +269,85 @@ describe('Test video channels', function () {
262 }) 269 })
263 270
264 it('Should update video channel avatar', async function () { 271 it('Should update video channel avatar', async function () {
265 this.timeout(5000) 272 this.timeout(15000)
266 273
267 const fixture = 'avatar.png' 274 const fixture = 'avatar.png'
268 275
269 await updateVideoChannelAvatar({ 276 await updateVideoChannelImage({
270 url: servers[0].url, 277 url: servers[0].url,
271 accessToken: servers[0].accessToken, 278 accessToken: servers[0].accessToken,
272 videoChannelName: 'second_video_channel', 279 videoChannelName: 'second_video_channel',
273 fixture 280 fixture,
281 type: 'avatar'
274 }) 282 })
275 283
276 await waitJobs(servers) 284 await waitJobs(servers)
285
286 for (const server of servers) {
287 const videoChannel = await findChannel(server, secondVideoChannelId)
288
289 await testImage(server.url, 'avatar-resized', videoChannel.avatar.path, '.png')
290 }
277 }) 291 })
278 292
279 it('Should have video channel avatar updated', async function () { 293 it('Should update video channel banner', async function () {
294 this.timeout(15000)
295
296 const fixture = 'banner.jpg'
297
298 await updateVideoChannelImage({
299 url: servers[0].url,
300 accessToken: servers[0].accessToken,
301 videoChannelName: 'second_video_channel',
302 fixture,
303 type: 'banner'
304 })
305
306 await waitJobs(servers)
307
280 for (const server of servers) { 308 for (const server of servers) {
281 const res = await getVideoChannelsList(server.url, 0, 1, '-name') 309 const videoChannel = await findChannel(server, secondVideoChannelId)
282 310
283 const videoChannel = res.body.data.find(c => c.id === secondVideoChannelId) 311 await testImage(server.url, 'banner-resized', videoChannel.banner.path)
312 }
313 })
284 314
285 await testImage(server.url, 'avatar-resized', videoChannel.avatar.path, '.png') 315 it('Should delete the video channel avatar', async function () {
316 this.timeout(15000)
317
318 await deleteVideoChannelImage({
319 url: servers[0].url,
320 accessToken: servers[0].accessToken,
321 videoChannelName: 'second_video_channel',
322 type: 'avatar'
323 })
324
325 await waitJobs(servers)
326
327 for (const server of servers) {
328 const videoChannel = await findChannel(server, secondVideoChannelId)
329
330 expect(videoChannel.avatar).to.be.null
286 } 331 }
287 }) 332 })
288 333
289 it('Should get video channel', async function () { 334 it('Should delete the video channel banner', async function () {
290 const res = await getVideoChannel(servers[0].url, 'second_video_channel') 335 this.timeout(15000)
336
337 await deleteVideoChannelImage({
338 url: servers[0].url,
339 accessToken: servers[0].accessToken,
340 videoChannelName: 'second_video_channel',
341 type: 'banner'
342 })
291 343
292 const videoChannel = res.body 344 await waitJobs(servers)
293 expect(videoChannel.name).to.equal('second_video_channel') 345
294 expect(videoChannel.displayName).to.equal('video channel updated') 346 for (const server of servers) {
295 expect(videoChannel.description).to.equal('video channel description updated') 347 const videoChannel = await findChannel(server, secondVideoChannelId)
296 expect(videoChannel.support).to.equal('video channel support text updated') 348
349 expect(videoChannel.banner).to.be.null
350 }
297 }) 351 })
298 352
299 it('Should list the second video channel videos', async function () { 353 it('Should list the second video channel videos', async function () {
diff --git a/server/tests/fixtures/banner-resized.jpg b/server/tests/fixtures/banner-resized.jpg
new file mode 100644
index 000000000..13ea422cb
--- /dev/null
+++ b/server/tests/fixtures/banner-resized.jpg
Binary files differ
diff --git a/server/tests/fixtures/banner.jpg b/server/tests/fixtures/banner.jpg
new file mode 100644
index 000000000..e5f284f59
--- /dev/null
+++ b/server/tests/fixtures/banner.jpg
Binary files differ
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts
index ee4faa35d..cf3e7ae34 100644
--- a/server/typings/express/index.d.ts
+++ b/server/typings/express/index.d.ts
@@ -3,7 +3,6 @@ import {
3 MAbuseMessage, 3 MAbuseMessage,
4 MAbuseReporter, 4 MAbuseReporter,
5 MAccountBlocklist, 5 MAccountBlocklist,
6 MActorFollowActors,
7 MActorFollowActorsDefault, 6 MActorFollowActorsDefault,
8 MActorUrl, 7 MActorUrl,
9 MChannelBannerAccountDefault, 8 MChannelBannerAccountDefault,
diff --git a/shared/extra-utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts
index 3e773ee03..8b5cddf4a 100644
--- a/shared/extra-utils/requests/requests.ts
+++ b/shared/extra-utils/requests/requests.ts
@@ -152,11 +152,12 @@ function makeHTMLRequest (url: string, path: string) {
152 .expect(HttpStatusCode.OK_200) 152 .expect(HttpStatusCode.OK_200)
153} 153}
154 154
155function updateAvatarRequest (options: { 155function updateImageRequest (options: {
156 url: string 156 url: string
157 path: string 157 path: string
158 accessToken: string 158 accessToken: string
159 fixture: string 159 fixture: string
160 fieldname: string
160}) { 161}) {
161 let filePath = '' 162 let filePath = ''
162 if (isAbsolute(options.fixture)) { 163 if (isAbsolute(options.fixture)) {
@@ -170,7 +171,7 @@ function updateAvatarRequest (options: {
170 path: options.path, 171 path: options.path,
171 token: options.accessToken, 172 token: options.accessToken,
172 fields: {}, 173 fields: {},
173 attaches: { avatarfile: filePath }, 174 attaches: { [options.fieldname]: filePath },
174 statusCodeExpected: HttpStatusCode.OK_200 175 statusCodeExpected: HttpStatusCode.OK_200
175 }) 176 })
176} 177}
@@ -191,5 +192,5 @@ export {
191 makePutBodyRequest, 192 makePutBodyRequest,
192 makeDeleteRequest, 193 makeDeleteRequest,
193 makeRawRequest, 194 makeRawRequest,
194 updateAvatarRequest 195 updateImageRequest
195} 196}
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index db532dbb0..6040dd9c0 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -4,7 +4,7 @@ import { UserUpdateMe } from '../../models/users'
4import { UserAdminFlag } from '../../models/users/user-flag.model' 4import { UserAdminFlag } from '../../models/users/user-flag.model'
5import { UserRegister } from '../../models/users/user-register.model' 5import { UserRegister } from '../../models/users/user-register.model'
6import { UserRole } from '../../models/users/user-role' 6import { UserRole } from '../../models/users/user-role'
7import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' 7import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateImageRequest } from '../requests/requests'
8import { ServerInfo } from '../server/servers' 8import { ServerInfo } from '../server/servers'
9import { userLogin } from './login' 9import { userLogin } from './login'
10import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 10import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
@@ -275,7 +275,7 @@ function updateMyAvatar (options: {
275}) { 275}) {
276 const path = '/api/v1/users/me/avatar/pick' 276 const path = '/api/v1/users/me/avatar/pick'
277 277
278 return updateAvatarRequest(Object.assign(options, { path })) 278 return updateImageRequest({ ...options, path, fieldname: 'avatarfile' })
279} 279}
280 280
281function updateUser (options: { 281function updateUser (options: {
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 3ff445c2a..d0dfb5856 100644
--- a/shared/extra-utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -3,7 +3,7 @@
3import * as request from 'supertest' 3import * as request from 'supertest'
4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' 4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
5import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' 5import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
6import { makeGetRequest, updateAvatarRequest } from '../requests/requests' 6import { makeDeleteRequest, makeGetRequest, updateImageRequest } from '../requests/requests'
7import { ServerInfo } from '../server/servers' 7import { ServerInfo } from '../server/servers'
8import { User } from '../../models/users/user.model' 8import { User } from '../../models/users/user.model'
9import { getMyUserInformation } from '../users/users' 9import { getMyUserInformation } from '../users/users'
@@ -129,16 +129,32 @@ function getVideoChannel (url: string, channelName: string) {
129 .expect('Content-Type', /json/) 129 .expect('Content-Type', /json/)
130} 130}
131 131
132function updateVideoChannelAvatar (options: { 132function updateVideoChannelImage (options: {
133 url: string 133 url: string
134 accessToken: string 134 accessToken: string
135 fixture: string 135 fixture: string
136 videoChannelName: string | number 136 videoChannelName: string | number
137 type: 'avatar' | 'banner'
137}) { 138}) {
139 const path = `/api/v1/video-channels/${options.videoChannelName}/${options.type}/pick`
138 140
139 const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick' 141 return updateImageRequest({ ...options, path, fieldname: options.type + 'file' })
142}
143
144function deleteVideoChannelImage (options: {
145 url: string
146 accessToken: string
147 videoChannelName: string | number
148 type: 'avatar' | 'banner'
149}) {
150 const path = `/api/v1/video-channels/${options.videoChannelName}/${options.type}`
140 151
141 return updateAvatarRequest(Object.assign(options, { path })) 152 return makeDeleteRequest({
153 url: options.url,
154 token: options.accessToken,
155 path,
156 statusCodeExpected: 204
157 })
142} 158}
143 159
144function setDefaultVideoChannel (servers: ServerInfo[]) { 160function setDefaultVideoChannel (servers: ServerInfo[]) {
@@ -157,12 +173,13 @@ function setDefaultVideoChannel (servers: ServerInfo[]) {
157// --------------------------------------------------------------------------- 173// ---------------------------------------------------------------------------
158 174
159export { 175export {
160 updateVideoChannelAvatar, 176 updateVideoChannelImage,
161 getVideoChannelsList, 177 getVideoChannelsList,
162 getAccountVideoChannelsList, 178 getAccountVideoChannelsList,
163 addVideoChannel, 179 addVideoChannel,
164 updateVideoChannel, 180 updateVideoChannel,
165 deleteVideoChannel, 181 deleteVideoChannel,
166 getVideoChannel, 182 getVideoChannel,
167 setDefaultVideoChannel 183 setDefaultVideoChannel,
184 deleteVideoChannelImage
168} 185}