import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
import { sendUpdateActor } from '../../lib/activitypub/send'
import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
-import { createVideoChannel } from '../../lib/video-channel'
+import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
import { setAsyncActorKeys } from '../../lib/activitypub'
import { AccountModel } from '../../models/account/account'
const videoChannelFieldsSave = videoChannelInstance.toJSON()
const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
+ let doBulkVideoUpdate = false
try {
await sequelizeTypescript.transaction(async t => {
transaction: t
}
- if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.displayName)
- if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
- if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
+ if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
+ if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
+
+ if (videoChannelInfoToUpdate.support !== undefined) {
+ const oldSupportField = videoChannelInstance.support
+ videoChannelInstance.support = videoChannelInfoToUpdate.support
+
+ if (videoChannelInfoToUpdate.bulkVideosSupportUpdate === true && oldSupportField !== videoChannelInfoToUpdate.support) {
+ doBulkVideoUpdate = true
+ await VideoModel.bulkUpdateSupportField(videoChannelInstance, t)
+ }
+ }
const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
await sendUpdateActor(videoChannelInstanceUpdated, t)
new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
oldVideoChannelAuditKeys
)
+
logger.info('Video channel %s updated.', videoChannelInstance.Actor.url)
})
} catch (err) {
throw err
}
- return res.type('json').status(204).end()
+ res.type('json').status(204).end()
+
+ // Don't process in a transaction, and after the response because it could be long
+ if (doBulkVideoUpdate) {
+ await federateAllVideosOfChannel(videoChannelInstance)
+ }
}
async function removeVideoChannel (req: express.Request, res: express.Response) {
commentObject.inReplyTo,
{ err }
)
+ return
}
const { comment, created } = await addVideoComment(video, commentObject.id)
import { VideoChannelCreate } from '../../shared/models'
import { AccountModel } from '../models/account/account'
import { VideoChannelModel } from '../models/video/video-channel'
-import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub'
+import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
+import { VideoModel } from '../models/video/video'
async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
const uuid = uuidv4()
return videoChannelCreated
}
+async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
+ const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
+
+ for (const videoId of videoIds) {
+ const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
+
+ await federateVideoIfNeeded(video, false)
+ }
+}
+
// ---------------------------------------------------------------------------
export {
- createVideoChannel
+ createVideoChannel,
+ federateAllVideosOfChannel
}
import { areValidationErrors } from '../utils'
import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
import { ActorModel } from '../../../models/activitypub/actor'
+import { isBooleanValid } from '../../../helpers/custom-validators/misc'
const videoChannelsAddValidator = [
body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
const videoChannelsUpdateValidator = [
param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
- body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
- body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
- body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
+ body('displayName')
+ .optional()
+ .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
+ body('description')
+ .optional()
+ .custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
+ body('support')
+ .optional()
+ .custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
+ body('bulkVideosSupportUpdate')
+ .optional()
+ .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
.then(results => results.length === 1)
}
+ static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) {
+ const options = {
+ where: {
+ channelId: videoChannel.id
+ },
+ transaction: t
+ }
+
+ return VideoModel.update({ support: videoChannel.support }, options)
+ }
+
+ static getAllIdsFromChannel (videoChannel: VideoChannelModel) {
+ const query = {
+ attributes: [ 'id' ],
+ where: {
+ channelId: videoChannel.id
+ }
+ }
+
+ return VideoModel.findAll(query)
+ .then(videos => videos.map(v => v.id))
+ }
+
// threshold corresponds to how many video the field should have to be returned
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
const serverActor = await getServerActor()
checkBadStartPagination
} from '../../../../shared/extra-utils/requests/check-api-params'
import { join } from 'path'
+import { VideoChannelUpdate } from '../../../../shared/models/videos'
const expect = chai.expect
})
describe('When updating a video channel', function () {
- const baseCorrectParams = {
+ const baseCorrectParams: VideoChannelUpdate = {
displayName: 'hello',
- description: 'super description'
+ description: 'super description',
+ support: 'toto',
+ bulkVideosSupportUpdate: false
}
let path: string
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
+ it('Should fail with a bad bulkVideosSupportUpdate field', async function () {
+ const fields = immutableAssign(baseCorrectParams, { bulkVideosSupportUpdate: 'super' })
+ await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+ })
+
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: server.url,
import * as chai from 'chai'
import 'mocha'
-import { User, Video, VideoChannel } from '../../../../shared/index'
+import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/index'
import {
cleanupTests,
createUser,
doubleFollow,
- flushAndRunMultipleServers,
+ flushAndRunMultipleServers, getVideo,
getVideoChannelVideos,
testImage,
updateVideo,
// The channel is 1 is propagated to servers 2
{
- const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name', channelId: secondVideoChannelId })
+ const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' }
+ const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributesArg)
videoUUID = res.body.video.uuid
}
})
it('Should update video channel', async function () {
- this.timeout(5000)
+ this.timeout(15000)
const videoChannelAttributes = {
displayName: 'video channel updated',
description: 'video channel description updated',
- support: 'video channel support text updated'
+ support: 'support updated'
}
await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
expect(res.body.data[0].name).to.equal('second_video_channel')
expect(res.body.data[0].displayName).to.equal('video channel updated')
expect(res.body.data[0].description).to.equal('video channel description updated')
- expect(res.body.data[0].support).to.equal('video channel support text updated')
+ expect(res.body.data[0].support).to.equal('support updated')
+ }
+ })
+
+ it('Should not have updated the video support field', async function () {
+ for (const server of servers) {
+ const res = await getVideo(server.url, videoUUID)
+ const video: VideoDetails = res.body
+
+ expect(video.support).to.equal('video support field')
+ }
+ })
+
+ it('Should update the channel support field and update videos too', async function () {
+ this.timeout(35000)
+
+ const videoChannelAttributes = {
+ support: 'video channel support text updated',
+ bulkVideosSupportUpdate: true
+ }
+
+ await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
+
+ await waitJobs(servers)
+
+ for (const server of servers) {
+ const res = await getVideo(server.url, videoUUID)
+ const video: VideoDetails = res.body
+
+ expect(video.support).to.equal(videoChannelAttributes.support)
}
})
attributes: VideoChannelUpdate,
expectedStatus = 204
) {
- const body = {}
+ const body: any = {}
const path = '/api/v1/video-channels/' + channelName
- if (attributes.displayName) body['displayName'] = attributes.displayName
- if (attributes.description) body['description'] = attributes.description
- if (attributes.support) body['support'] = attributes.support
+ if (attributes.displayName) body.displayName = attributes.displayName
+ if (attributes.description) body.description = attributes.description
+ if (attributes.support) body.support = attributes.support
+ if (attributes.bulkVideosSupportUpdate) body.bulkVideosSupportUpdate = attributes.bulkVideosSupportUpdate
return request(url)
.put(path)
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.field('name', attributes.name)
+ .field('support', attributes.support)
.field('nsfw', JSON.stringify(attributes.nsfw))
.field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
.field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
export interface VideoChannelUpdate {
- displayName: string
+ displayName?: string
description?: string
support?: string
+
+ bulkVideosSupportUpdate?: boolean
}
'204':
$ref: '#/paths/~1users~1me/put/responses/204'
requestBody:
- $ref: '#/components/requestBodies/VideoChannelInput'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/VideoChannelCreate'
'/video-channels/{channelHandle}':
get:
summary: Get a video channel by its id
'204':
$ref: '#/paths/~1users~1me/put/responses/204'
requestBody:
- $ref: '#/components/requestBodies/VideoChannelInput'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/VideoChannelUpdate'
delete:
summary: Delete a video channel by its id
security:
type: array
items:
type: string
- requestBodies:
- VideoChannelInput:
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/VideoChannelInput'
securitySchemes:
OAuth2:
description: >
- username
- password
- email
- VideoChannelInput:
+ VideoChannelCreate:
properties:
name:
type: string
+ displayName:
+ type: string
description:
type: string
+ support:
+ type: string
+ required:
+ - name
+ - displayName
+ VideoChannelUpdate:
+ properties:
+ displayName:
+ type: string
+ description:
+ type: string
+ support:
+ type: string
+ bulkVideosSupportUpdate:
+ type: boolean
+ description: 'Update all videos support field of this channel'