Server: Bulk update videos support field

This commit is contained in:
Chocobozzz 2019-05-31 16:30:11 +02:00
parent 9977c12838
commit 7d14d4d2ca
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
11 changed files with 154 additions and 32 deletions

View File

@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel'
import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
import { sendUpdateActor } from '../../lib/activitypub/send' import { sendUpdateActor } from '../../lib/activitypub/send'
import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 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 { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
import { setAsyncActorKeys } from '../../lib/activitypub' import { setAsyncActorKeys } from '../../lib/activitypub'
import { AccountModel } from '../../models/account/account' import { AccountModel } from '../../models/account/account'
@ -160,6 +160,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
const videoChannelFieldsSave = videoChannelInstance.toJSON() const videoChannelFieldsSave = videoChannelInstance.toJSON()
const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
const videoChannelInfoToUpdate = req.body as VideoChannelUpdate const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
let doBulkVideoUpdate = false
try { try {
await sequelizeTypescript.transaction(async t => { await sequelizeTypescript.transaction(async t => {
@ -167,9 +168,18 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
transaction: t transaction: t
} }
if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.displayName) if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
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) const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
await sendUpdateActor(videoChannelInstanceUpdated, t) await sendUpdateActor(videoChannelInstanceUpdated, t)
@ -179,6 +189,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()), new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
oldVideoChannelAuditKeys oldVideoChannelAuditKeys
) )
logger.info('Video channel %s updated.', videoChannelInstance.Actor.url) logger.info('Video channel %s updated.', videoChannelInstance.Actor.url)
}) })
} catch (err) { } catch (err) {
@ -192,7 +203,12 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
throw 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) { async function removeVideoChannel (req: express.Request, res: express.Response) {

View File

@ -87,6 +87,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act
commentObject.inReplyTo, commentObject.inReplyTo,
{ err } { err }
) )
return
} }
const { comment, created } = await addVideoComment(video, commentObject.id) const { comment, created } = await addVideoComment(video, commentObject.id)

View File

@ -3,7 +3,8 @@ import * as uuidv4 from 'uuid/v4'
import { VideoChannelCreate } from '../../shared/models' import { VideoChannelCreate } from '../../shared/models'
import { AccountModel } from '../models/account/account' import { AccountModel } from '../models/account/account'
import { VideoChannelModel } from '../models/video/video-channel' 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) { async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
const uuid = uuidv4() const uuid = uuidv4()
@ -33,8 +34,19 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
return videoChannelCreated 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 { export {
createVideoChannel createVideoChannel,
federateAllVideosOfChannel
} }

View File

@ -14,6 +14,7 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
import { areValidationErrors } from '../utils' import { areValidationErrors } from '../utils'
import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { isBooleanValid } from '../../../helpers/custom-validators/misc'
const videoChannelsAddValidator = [ const videoChannelsAddValidator = [
body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
@ -40,9 +41,18 @@ const videoChannelsAddValidator = [
const videoChannelsUpdateValidator = [ const videoChannelsUpdateValidator = [
param('nameWithHost').exists().withMessage('Should have an video channel name with host'), 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('displayName')
body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), .optional()
body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), .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) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })

View File

@ -1515,6 +1515,29 @@ export class VideoModel extends Model<VideoModel> {
.then(results => results.length === 1) .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 // threshold corresponds to how many video the field should have to be returned
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
const serverActor = await getServerActor() const serverActor = await getServerActor()

View File

@ -24,6 +24,7 @@ import {
checkBadStartPagination checkBadStartPagination
} from '../../../../shared/extra-utils/requests/check-api-params' } from '../../../../shared/extra-utils/requests/check-api-params'
import { join } from 'path' import { join } from 'path'
import { VideoChannelUpdate } from '../../../../shared/models/videos'
const expect = chai.expect const expect = chai.expect
@ -169,9 +170,11 @@ describe('Test video channels API validator', function () {
}) })
describe('When updating a video channel', function () { describe('When updating a video channel', function () {
const baseCorrectParams = { const baseCorrectParams: VideoChannelUpdate = {
displayName: 'hello', displayName: 'hello',
description: 'super description' description: 'super description',
support: 'toto',
bulkVideosSupportUpdate: false
} }
let path: string let path: string
@ -214,6 +217,11 @@ describe('Test video channels API validator', function () {
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 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 () { it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({ await makePutBodyRequest({
url: server.url, url: server.url,

View File

@ -2,12 +2,12 @@
import * as chai from 'chai' import * as chai from 'chai'
import 'mocha' import 'mocha'
import { User, Video, VideoChannel } from '../../../../shared/index' import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/index'
import { import {
cleanupTests, cleanupTests,
createUser, createUser,
doubleFollow, doubleFollow,
flushAndRunMultipleServers, flushAndRunMultipleServers, getVideo,
getVideoChannelVideos, getVideoChannelVideos,
testImage, testImage,
updateVideo, updateVideo,
@ -79,7 +79,8 @@ describe('Test video channels', function () {
// The channel is 1 is propagated to servers 2 // 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 videoUUID = res.body.video.uuid
} }
@ -201,12 +202,12 @@ describe('Test video channels', function () {
}) })
it('Should update video channel', async function () { it('Should update video channel', async function () {
this.timeout(5000) this.timeout(15000)
const videoChannelAttributes = { const videoChannelAttributes = {
displayName: 'video channel updated', displayName: 'video channel updated',
description: 'video channel description 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) await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
@ -224,7 +225,36 @@ describe('Test video channels', function () {
expect(res.body.data[0].name).to.equal('second_video_channel') 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].displayName).to.equal('video channel updated')
expect(res.body.data[0].description).to.equal('video channel description 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)
} }
}) })

View File

@ -74,12 +74,13 @@ function updateVideoChannel (
attributes: VideoChannelUpdate, attributes: VideoChannelUpdate,
expectedStatus = 204 expectedStatus = 204
) { ) {
const body = {} const body: any = {}
const path = '/api/v1/video-channels/' + channelName const path = '/api/v1/video-channels/' + channelName
if (attributes.displayName) body['displayName'] = attributes.displayName if (attributes.displayName) body.displayName = attributes.displayName
if (attributes.description) body['description'] = attributes.description if (attributes.description) body.description = attributes.description
if (attributes.support) body['support'] = attributes.support if (attributes.support) body.support = attributes.support
if (attributes.bulkVideosSupportUpdate) body.bulkVideosSupportUpdate = attributes.bulkVideosSupportUpdate
return request(url) return request(url)
.put(path) .put(path)

View File

@ -355,6 +355,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
.set('Accept', 'application/json') .set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken) .set('Authorization', 'Bearer ' + accessToken)
.field('name', attributes.name) .field('name', attributes.name)
.field('support', attributes.support)
.field('nsfw', JSON.stringify(attributes.nsfw)) .field('nsfw', JSON.stringify(attributes.nsfw))
.field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
.field('downloadEnabled', JSON.stringify(attributes.downloadEnabled)) .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))

View File

@ -1,5 +1,7 @@
export interface VideoChannelUpdate { export interface VideoChannelUpdate {
displayName: string displayName?: string
description?: string description?: string
support?: string support?: string
bulkVideosSupportUpdate?: boolean
} }

View File

@ -1322,7 +1322,10 @@ paths:
'204': '204':
$ref: '#/paths/~1users~1me/put/responses/204' $ref: '#/paths/~1users~1me/put/responses/204'
requestBody: requestBody:
$ref: '#/components/requestBodies/VideoChannelInput' content:
application/json:
schema:
$ref: '#/components/schemas/VideoChannelCreate'
'/video-channels/{channelHandle}': '/video-channels/{channelHandle}':
get: get:
summary: Get a video channel by its id summary: Get a video channel by its id
@ -1349,7 +1352,10 @@ paths:
'204': '204':
$ref: '#/paths/~1users~1me/put/responses/204' $ref: '#/paths/~1users~1me/put/responses/204'
requestBody: requestBody:
$ref: '#/components/requestBodies/VideoChannelInput' content:
application/json:
schema:
$ref: '#/components/schemas/VideoChannelUpdate'
delete: delete:
summary: Delete a video channel by its id summary: Delete a video channel by its id
security: security:
@ -1775,12 +1781,6 @@ components:
type: array type: array
items: items:
type: string type: string
requestBodies:
VideoChannelInput:
content:
application/json:
schema:
$ref: '#/components/schemas/VideoChannelInput'
securitySchemes: securitySchemes:
OAuth2: OAuth2:
description: > description: >
@ -2294,10 +2294,28 @@ components:
- username - username
- password - password
- email - email
VideoChannelInput: VideoChannelCreate:
properties: properties:
name: name:
type: string type: string
displayName:
type: string
description: description:
type: string 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'