Implement support field in video and video channel

This commit is contained in:
Chocobozzz 2018-02-15 14:46:26 +01:00
parent 34cbef8c6c
commit 2422c46b27
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
49 changed files with 490 additions and 146 deletions

View File

@ -8,6 +8,7 @@ export class Account implements ServerAccount {
url: string url: string
name: string name: string
displayName: string displayName: string
description: string
host: string host: string
followingCount: number followingCount: number
followersCount: number followersCount: number

View File

@ -18,6 +18,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
languageLabel: string languageLabel: string
language: number language: number
description: string description: string
support: string
duration: number duration: number
durationLabel: string durationLabel: string
id: number id: number

View File

@ -9,7 +9,7 @@ import { logger } from '../../helpers/logger'
import { createReqFiles, getFormattedObjects } from '../../helpers/utils' import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
import { updateActorAvatarInstance } from '../../lib/activitypub' import { updateActorAvatarInstance } from '../../lib/activitypub'
import { sendUpdateUser } from '../../lib/activitypub/send' import { sendUpdateActor } from '../../lib/activitypub/send'
import { Emailer } from '../../lib/emailer' import { Emailer } from '../../lib/emailer'
import { Redis } from '../../lib/redis' import { Redis } from '../../lib/redis'
import { createUserAccountAndChannel } from '../../lib/user' import { createUserAccountAndChannel } from '../../lib/user'
@ -270,15 +270,21 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
const body: UserUpdateMe = req.body const body: UserUpdateMe = req.body
const user = res.locals.oauth.token.user const user: UserModel = res.locals.oauth.token.user
if (body.password !== undefined) user.password = body.password if (body.password !== undefined) user.password = body.password
if (body.email !== undefined) user.email = body.email if (body.email !== undefined) user.email = body.email
if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
await user.save() await sequelizeTypescript.transaction(async t => {
await sendUpdateUser(user, undefined) await user.save({ transaction: t })
if (body.description !== undefined) user.Account.description = body.description
await user.Account.save({ transaction: t })
await sendUpdateActor(user.Account, t)
})
return res.sendStatus(204) return res.sendStatus(204)
} }
@ -297,7 +303,7 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
await updatedActor.save({ transaction: t }) await updatedActor.save({ transaction: t })
await sendUpdateUser(user, t) await sendUpdateActor(user.Account, t)
return updatedActor.Avatar return updatedActor.Avatar
}) })

View File

@ -5,6 +5,7 @@ import { logger } from '../../../helpers/logger'
import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils' import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils'
import { sequelizeTypescript } from '../../../initializers' import { sequelizeTypescript } from '../../../initializers'
import { setAsyncActorKeys } from '../../../lib/activitypub' import { setAsyncActorKeys } from '../../../lib/activitypub'
import { sendUpdateActor } from '../../../lib/activitypub/send'
import { createVideoChannel } from '../../../lib/video-channel' import { createVideoChannel } from '../../../lib/video-channel'
import { import {
asyncMiddleware, authenticate, listVideoAccountChannelsValidator, paginationValidator, setDefaultSort, setDefaultPagination, asyncMiddleware, authenticate, listVideoAccountChannelsValidator, paginationValidator, setDefaultSort, setDefaultPagination,
@ -80,23 +81,28 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R
errorMessage: 'Cannot insert the video video channel with many retries.' errorMessage: 'Cannot insert the video video channel with many retries.'
} }
await retryTransactionWrapper(addVideoChannel, options) const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
return res.json({
// TODO : include Location of the new video channel -> 201 videoChannel: {
return res.type('json').status(204).end() id: videoChannel.id
}
}).end()
} }
async function addVideoChannel (req: express.Request, res: express.Response) { async function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body const videoChannelInfo: VideoChannelCreate = req.body
const account: AccountModel = res.locals.oauth.token.User.Account const account: AccountModel = res.locals.oauth.token.User.Account
const videoChannelCreated = await sequelizeTypescript.transaction(async t => { const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
return createVideoChannel(videoChannelInfo, account, t) return createVideoChannel(videoChannelInfo, account, t)
}) })
setAsyncActorKeys(videoChannelCreated.Actor) setAsyncActorKeys(videoChannelCreated.Actor)
.catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, err))
logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
return videoChannelCreated
} }
async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
@ -123,11 +129,10 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
await videoChannelInstance.save(sequelizeOptions) const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
await sendUpdateActor(videoChannelInstanceUpdated, t)
// TODO
// await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
}) })
logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)

View File

@ -178,6 +178,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
commentsEnabled: videoInfo.commentsEnabled, commentsEnabled: videoInfo.commentsEnabled,
nsfw: videoInfo.nsfw, nsfw: videoInfo.nsfw,
description: videoInfo.description, description: videoInfo.description,
support: videoInfo.support,
privacy: videoInfo.privacy, privacy: videoInfo.privacy,
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
channelId: res.locals.videoChannel.id channelId: res.locals.videoChannel.id
@ -306,6 +307,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10)) if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10))
if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support)
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled) if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)

View File

@ -19,7 +19,8 @@ function activityPubContextify <T> (data: T) {
'language': 'http://schema.org/inLanguage', 'language': 'http://schema.org/inLanguage',
'views': 'http://schema.org/Number', 'views': 'http://schema.org/Number',
'size': 'http://schema.org/Number', 'size': 'http://schema.org/Number',
'commentsEnabled': 'http://schema.org/Boolean' 'commentsEnabled': 'http://schema.org/Boolean',
'support': 'http://schema.org/Text'
}, },
{ {
likes: { likes: {

View File

@ -3,12 +3,16 @@ import { Response } from 'express'
import 'express-validator' import 'express-validator'
import * as validator from 'validator' import * as validator from 'validator'
import { AccountModel } from '../../models/account/account' import { AccountModel } from '../../models/account/account'
import { isUserUsernameValid } from './users' import { isUserDescriptionValid, isUserUsernameValid } from './users'
function isAccountNameValid (value: string) { function isAccountNameValid (value: string) {
return isUserUsernameValid(value) return isUserUsernameValid(value)
} }
function isAccountDescriptionValid (value: string) {
return isUserDescriptionValid(value)
}
function isAccountIdExist (id: number | string, res: Response) { function isAccountIdExist (id: number | string, res: Response) {
let promise: Bluebird<AccountModel> let promise: Bluebird<AccountModel>
@ -48,5 +52,6 @@ async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
export { export {
isAccountIdExist, isAccountIdExist,
isLocalAccountNameExist, isLocalAccountNameExist,
isAccountDescriptionValid,
isAccountNameValid isAccountNameValid
} }

View File

@ -21,6 +21,10 @@ function isUserUsernameValid (value: string) {
return exists(value) && validator.matches(value, new RegExp(`^[a-z0-9._]{${min},${max}}$`)) return exists(value) && validator.matches(value, new RegExp(`^[a-z0-9._]{${min},${max}}$`))
} }
function isUserDescriptionValid (value: string) {
return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.DESCRIPTION))
}
function isBoolean (value: any) { function isBoolean (value: any) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
} }
@ -54,5 +58,6 @@ export {
isUserUsernameValid, isUserUsernameValid,
isUserDisplayNSFWValid, isUserDisplayNSFWValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserDescriptionValid,
isAvatarFile isAvatarFile
} }

View File

@ -16,6 +16,10 @@ function isVideoChannelNameValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME) return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME)
} }
function isVideoChannelSupportValid (value: string) {
return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT))
}
async function isVideoChannelExist (id: string, res: express.Response) { async function isVideoChannelExist (id: string, res: express.Response) {
let videoChannel: VideoChannelModel let videoChannel: VideoChannelModel
if (validator.isInt(id)) { if (validator.isInt(id)) {
@ -41,5 +45,6 @@ async function isVideoChannelExist (id: string, res: express.Response) {
export { export {
isVideoChannelDescriptionValid, isVideoChannelDescriptionValid,
isVideoChannelNameValid, isVideoChannelNameValid,
isVideoChannelSupportValid,
isVideoChannelExist isVideoChannelExist
} }

View File

@ -42,6 +42,10 @@ function isVideoDescriptionValid (value: string) {
return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
} }
function isVideoSupportValid (value: string) {
return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
}
function isVideoNameValid (value: string) { function isVideoNameValid (value: string) {
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
} }
@ -140,5 +144,6 @@ export {
isVideoFileResolutionValid, isVideoFileResolutionValid,
isVideoFileSizeValid, isVideoFileSizeValid,
isVideoExist, isVideoExist,
isVideoImage isVideoImage,
isVideoSupportValid
} }

View File

@ -12,7 +12,7 @@ let config: IConfig = require('config')
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 190 const LAST_MIGRATION_VERSION = 195
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -168,6 +168,7 @@ const CONSTRAINTS_FIELDS = {
USERS: { USERS: {
USERNAME: { min: 3, max: 20 }, // Length USERNAME: { min: 3, max: 20 }, // Length
PASSWORD: { min: 6, max: 255 }, // Length PASSWORD: { min: 6, max: 255 }, // Length
DESCRIPTION: { min: 3, max: 250 }, // Length
VIDEO_QUOTA: { min: -1 } VIDEO_QUOTA: { min: -1 }
}, },
VIDEO_ABUSES: { VIDEO_ABUSES: {
@ -176,12 +177,14 @@ const CONSTRAINTS_FIELDS = {
VIDEO_CHANNELS: { VIDEO_CHANNELS: {
NAME: { min: 3, max: 120 }, // Length NAME: { min: 3, max: 120 }, // Length
DESCRIPTION: { min: 3, max: 250 }, // Length DESCRIPTION: { min: 3, max: 250 }, // Length
SUPPORT: { min: 3, max: 300 }, // Length
URL: { min: 3, max: 2000 } // Length URL: { min: 3, max: 2000 } // Length
}, },
VIDEOS: { VIDEOS: {
NAME: { min: 3, max: 120 }, // Length NAME: { min: 3, max: 120 }, // Length
TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
DESCRIPTION: { min: 3, max: 3000 }, // Length DESCRIPTION: { min: 3, max: 10000 }, // Length
SUPPORT: { min: 3, max: 300 }, // Length
IMAGE: { IMAGE: {
EXTNAME: [ '.jpg', '.jpeg' ], EXTNAME: [ '.jpg', '.jpeg' ],
FILE_SIZE: { FILE_SIZE: {

View File

@ -0,0 +1,53 @@
import * as Sequelize from 'sequelize'
import { CONSTRAINTS_FIELDS } from '../index'
async function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('video', 'support', data)
}
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.SUPPORT.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('videoChannel', 'support', data)
}
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('account', 'description', data)
}
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.changeColumn('video', 'description', data)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export {
up,
down
}

View File

@ -225,12 +225,10 @@ function saveActorAndServerAndModelIfNotExist (
}) })
if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
const account = await saveAccount(actorCreated, result, t) actorCreated.Account = await saveAccount(actorCreated, result, t)
actorCreated.Account = account
actorCreated.Account.Actor = actorCreated actorCreated.Account.Actor = actorCreated
} else if (actorCreated.type === 'Group') { // Video channel } else if (actorCreated.type === 'Group') { // Video channel
const videoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
actorCreated.VideoChannel = videoChannel
actorCreated.VideoChannel.Actor = actorCreated actorCreated.VideoChannel.Actor = actorCreated
} }
@ -242,6 +240,7 @@ type FetchRemoteActorResult = {
actor: ActorModel actor: ActorModel
name: string name: string
summary: string summary: string
support?: string
avatarName?: string avatarName?: string
attributedTo: ActivityPubAttributedTo[] attributedTo: ActivityPubAttributedTo[]
} }
@ -290,6 +289,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
name, name,
avatarName, avatarName,
summary: actorJSON.summary, summary: actorJSON.summary,
support: actorJSON.support,
attributedTo: actorJSON.attributedTo attributedTo: actorJSON.attributedTo
} }
} }
@ -298,6 +298,7 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t
const [ accountCreated ] = await AccountModel.findOrCreate({ const [ accountCreated ] = await AccountModel.findOrCreate({
defaults: { defaults: {
name: result.name, name: result.name,
description: result.summary,
actorId: actor.id actorId: actor.id
}, },
where: { where: {
@ -314,6 +315,7 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
defaults: { defaults: {
name: result.name, name: result.name,
description: result.summary, description: result.summary,
support: result.support,
actorId: actor.id, actorId: actor.id,
accountId: ownerActor.Account.id accountId: ownerActor.Account.id
}, },
@ -352,11 +354,14 @@ async function refreshActorIfNeeded (actor: ActorModel) {
await actor.save({ transaction: t }) await actor.save({ transaction: t })
actor.Account.set('name', result.name) actor.Account.set('name', result.name)
actor.Account.set('description', result.summary)
await actor.Account.save({ transaction: t }) await actor.Account.save({ transaction: t })
} else if (actor.VideoChannel) { } else if (actor.VideoChannel) {
await actor.save({ transaction: t }) await actor.save({ transaction: t })
actor.VideoChannel.set('name', result.name) actor.VideoChannel.set('name', result.name)
actor.VideoChannel.set('description', result.summary)
actor.VideoChannel.set('support', result.support)
await actor.VideoChannel.save({ transaction: t }) await actor.VideoChannel.save({ transaction: t })
} }

View File

@ -9,20 +9,24 @@ import { sequelizeTypescript } from '../../../initializers'
import { AccountModel } from '../../../models/account/account' import { AccountModel } from '../../../models/account/account'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { TagModel } from '../../../models/video/tag' import { TagModel } from '../../../models/video/tag'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoFileModel } from '../../../models/video/video-file' import { VideoFileModel } from '../../../models/video/video-file'
import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
import { import {
generateThumbnailFromUrl, getOrCreateAccountAndVideoAndChannel, videoActivityObjectToDBAttributes, generateThumbnailFromUrl,
getOrCreateAccountAndVideoAndChannel,
videoActivityObjectToDBAttributes,
videoFileActivityUrlToDBAttributes videoFileActivityUrlToDBAttributes
} from '../videos' } from '../videos'
async function processUpdateActivity (activity: ActivityUpdate) { async function processUpdateActivity (activity: ActivityUpdate) {
const actor = await getOrCreateActorAndServerAndModel(activity.actor) const actor = await getOrCreateActorAndServerAndModel(activity.actor)
const objectType = activity.object.type
if (activity.object.type === 'Video') { if (objectType === 'Video') {
return processUpdateVideo(actor, activity) return processUpdateVideo(actor, activity)
} else if (activity.object.type === 'Person') { } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
return processUpdateAccount(actor, activity) return processUpdateActor(actor, activity)
} }
return return
@ -75,6 +79,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
videoInstance.set('licence', videoData.licence) videoInstance.set('licence', videoData.licence)
videoInstance.set('language', videoData.language) videoInstance.set('language', videoData.language)
videoInstance.set('description', videoData.description) videoInstance.set('description', videoData.description)
videoInstance.set('support', videoData.support)
videoInstance.set('nsfw', videoData.nsfw) videoInstance.set('nsfw', videoData.nsfw)
videoInstance.set('commentsEnabled', videoData.commentsEnabled) videoInstance.set('commentsEnabled', videoData.commentsEnabled)
videoInstance.set('duration', videoData.duration) videoInstance.set('duration', videoData.duration)
@ -117,33 +122,36 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
} }
} }
function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) { function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
const options = { const options = {
arguments: [ actor, activity ], arguments: [ actor, activity ],
errorMessage: 'Cannot update the remote account with many retries' errorMessage: 'Cannot update the remote actor with many retries'
} }
return retryTransactionWrapper(updateRemoteAccount, options) return retryTransactionWrapper(updateRemoteActor, options)
} }
async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) { async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
const accountAttributesToUpdate = activity.object as ActivityPubActor const actorAttributesToUpdate = activity.object as ActivityPubActor
logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid) logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
let accountInstance: AccountModel let accountOrChannelInstance: AccountModel | VideoChannelModel
let actorFieldsSave: object let actorFieldsSave: object
let accountFieldsSave: object let accountOrChannelFieldsSave: object
// Fetch icon? // Fetch icon?
const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate) const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
try { try {
await sequelizeTypescript.transaction(async t => { await sequelizeTypescript.transaction(async t => {
actorFieldsSave = actor.toJSON() actorFieldsSave = actor.toJSON()
accountInstance = actor.Account
accountFieldsSave = actor.Account.toJSON()
await updateActorInstance(actor, accountAttributesToUpdate) if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
else accountOrChannelInstance = actor.Account
accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
await updateActorInstance(actor, actorAttributesToUpdate)
if (avatarName !== undefined) { if (avatarName !== undefined) {
await updateActorAvatarInstance(actor, avatarName, t) await updateActorAvatarInstance(actor, avatarName, t)
@ -151,18 +159,20 @@ async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate)
await actor.save({ transaction: t }) await actor.save({ transaction: t })
actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername) accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
await actor.Account.save({ transaction: t }) accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
await accountOrChannelInstance.save({ transaction: t })
}) })
logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid) logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
} catch (err) { } catch (err) {
if (actor !== undefined && actorFieldsSave !== undefined) { if (actor !== undefined && actorFieldsSave !== undefined) {
resetSequelizeInstance(actor, actorFieldsSave) resetSequelizeInstance(actor, actorFieldsSave)
} }
if (accountInstance !== undefined && accountFieldsSave !== undefined) { if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
resetSequelizeInstance(accountInstance, accountFieldsSave) resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
} }
// This is just a debug because we will retry the insert // This is just a debug because we will retry the insert

View File

@ -1,9 +1,10 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
import { VideoPrivacy } from '../../../../shared/models/videos' import { VideoPrivacy } from '../../../../shared/models/videos'
import { UserModel } from '../../../models/account/user' import { AccountModel } from '../../../models/account/account'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { getUpdateActivityPubUrl } from '../url' import { getUpdateActivityPubUrl } from '../url'
import { audiencify, broadcastToFollowers, getAudience } from './misc' import { audiencify, broadcastToFollowers, getAudience } from './misc'
@ -23,15 +24,23 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) {
return broadcastToFollowers(data, byActor, actorsInvolved, t) return broadcastToFollowers(data, byActor, actorsInvolved, t)
} }
async function sendUpdateUser (user: UserModel, t: Transaction) { async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) {
const byActor = user.Account.Actor const byActor = accountOrChannel.Actor
const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString())
const accountObject = user.Account.toActivityPubObject() const accountOrChannelObject = accountOrChannel.toActivityPubObject()
const audience = await getAudience(byActor, t) const audience = await getAudience(byActor, t)
const data = await updateActivityData(url, byActor, accountObject, t, audience) const data = await updateActivityData(url, byActor, accountOrChannelObject, t, audience)
let actorsInvolved: ActorModel[]
if (accountOrChannel instanceof AccountModel) {
// Actors that shared my videos are involved too
actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
} else {
// Actors that shared videos of my channel are involved too
actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t)
}
const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
actorsInvolved.push(byActor) actorsInvolved.push(byActor)
return broadcastToFollowers(data, byActor, actorsInvolved, t) return broadcastToFollowers(data, byActor, actorsInvolved, t)
@ -40,7 +49,7 @@ async function sendUpdateUser (user: UserModel, t: Transaction) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
sendUpdateUser, sendUpdateActor,
sendUpdateVideo sendUpdateVideo
} }

View File

@ -83,6 +83,11 @@ async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelMode
description = videoObject.content description = videoObject.content
} }
let support = null
if (videoObject.support) {
support = videoObject.support
}
return { return {
name: videoObject.name, name: videoObject.name,
uuid: videoObject.uuid, uuid: videoObject.uuid,
@ -91,6 +96,7 @@ async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelMode
licence, licence,
language, language,
description, description,
support,
nsfw: videoObject.sensitive, nsfw: videoObject.sensitive,
commentsEnabled: videoObject.commentsEnabled, commentsEnabled: videoObject.commentsEnabled,
channelId: videoChannel.id, channelId: videoChannel.id,

View File

@ -16,6 +16,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
const videoChannelData = { const videoChannelData = {
name: videoChannelInfo.name, name: videoChannelInfo.name,
description: videoChannelInfo.description, description: videoChannelInfo.description,
support: videoChannelInfo.support,
accountId: account.id, accountId: account.id,
actorId: actorInstanceCreated.id actorId: actorInstanceCreated.id
} }

View File

@ -7,6 +7,7 @@ import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import { import {
isAvatarFile, isAvatarFile,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserDescriptionValid,
isUserDisplayNSFWValid, isUserDisplayNSFWValid,
isUserPasswordValid, isUserPasswordValid,
isUserRoleValid, isUserRoleValid,
@ -97,6 +98,7 @@ const usersUpdateValidator = [
] ]
const usersUpdateMeValidator = [ const usersUpdateMeValidator = [
body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').optional().isEmail().withMessage('Should have a valid email attribute'), body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
body('displayNSFW').optional().custom(isUserDisplayNSFWValid).withMessage('Should have a valid display Not Safe For Work attribute'), body('displayNSFW').optional().custom(isUserDisplayNSFWValid).withMessage('Should have a valid display Not Safe For Work attribute'),

View File

@ -5,7 +5,7 @@ import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import { import {
isVideoChannelDescriptionValid, isVideoChannelExist, isVideoChannelDescriptionValid, isVideoChannelExist,
isVideoChannelNameValid isVideoChannelNameValid, isVideoChannelSupportValid
} from '../../helpers/custom-validators/video-channels' } from '../../helpers/custom-validators/video-channels'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { UserModel } from '../../models/account/user' import { UserModel } from '../../models/account/user'
@ -27,7 +27,8 @@ const listVideoAccountChannelsValidator = [
const videoChannelsAddValidator = [ const videoChannelsAddValidator = [
body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'), body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
body('description').custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
(req: express.Request, res: express.Response, next: express.NextFunction) => { (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body }) logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body })
@ -42,6 +43,7 @@ const videoChannelsUpdateValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'), body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
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

@ -14,7 +14,7 @@ import {
isVideoLicenceValid, isVideoLicenceValid,
isVideoNameValid, isVideoNameValid,
isVideoPrivacyValid, isVideoPrivacyValid,
isVideoRatingTypeValid, isVideoRatingTypeValid, isVideoSupportValid,
isVideoTagsValid isVideoTagsValid
} from '../../helpers/custom-validators/videos' } from '../../helpers/custom-validators/videos'
import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
@ -46,6 +46,7 @@ const videosAddValidator = [
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
body('nsfw').custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), body('nsfw').custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
body('support').optional().custom(isVideoSupportValid).withMessage('Should have a valid support text'),
body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
@ -116,6 +117,7 @@ const videosUpdateValidator = [
body('nsfw').optional().custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), body('nsfw').optional().custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
body('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
body('support').optional().custom(isVideoSupportValid).withMessage('Should have a valid support text'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
body('commentsEnabled').optional().custom(isBooleanValid).withMessage('Should have comments enabled boolean'), body('commentsEnabled').optional().custom(isBooleanValid).withMessage('Should have comments enabled boolean'),

View File

@ -1,16 +1,28 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { import {
AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Model, Table, AllowNull,
BeforeDestroy,
BelongsTo,
Column,
CreatedAt,
Default,
DefaultScope,
ForeignKey,
HasMany,
Is,
Model,
Table,
UpdatedAt UpdatedAt
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { Account } from '../../../shared/models/actors' import { Account } from '../../../shared/models/actors'
import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { sendDeleteActor } from '../../lib/activitypub/send' import { sendDeleteActor } from '../../lib/activitypub/send'
import { ActorModel } from '../activitypub/actor' import { ActorModel } from '../activitypub/actor'
import { ApplicationModel } from '../application/application' import { ApplicationModel } from '../application/application'
import { AvatarModel } from '../avatar/avatar' import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'
import { getSort } from '../utils' import { getSort, throwIfNotValid } from '../utils'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { VideoCommentModel } from '../video/video-comment' import { VideoCommentModel } from '../video/video-comment'
import { UserModel } from './user' import { UserModel } from './user'
@ -42,6 +54,12 @@ export class AccountModel extends Model<AccountModel> {
@Column @Column
name: string name: string
@AllowNull(true)
@Default(null)
@Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description'))
@Column
description: string
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date
@ -196,6 +214,7 @@ export class AccountModel extends Model<AccountModel> {
const account = { const account = {
id: this.id, id: this.id,
displayName: this.name, displayName: this.name,
description: this.description,
createdAt: this.createdAt, createdAt: this.createdAt,
updatedAt: this.updatedAt updatedAt: this.updatedAt
} }
@ -204,7 +223,11 @@ export class AccountModel extends Model<AccountModel> {
} }
toActivityPubObject () { toActivityPubObject () {
return this.Actor.toActivityPubObject(this.name, 'Account') const obj = this.Actor.toActivityPubObject(this.name, 'Account')
return Object.assign(obj, {
summary: this.description
})
} }
isOwned () { isOwned () {

View File

@ -2,14 +2,31 @@ import { values } from 'lodash'
import { extname } from 'path' import { extname } from 'path'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { import {
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes, AllowNull,
Table, UpdatedAt BelongsTo,
Column,
CreatedAt,
DataType,
Default,
DefaultScope,
ForeignKey,
HasMany,
HasOne,
Is,
IsUUID,
Model,
Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { ActivityPubActorType } from '../../../shared/models/activitypub' import { ActivityPubActorType } from '../../../shared/models/activitypub'
import { Avatar } from '../../../shared/models/avatars/avatar.model' import { Avatar } from '../../../shared/models/avatars/avatar.model'
import { activityPubContextify } from '../../helpers/activitypub' import { activityPubContextify } from '../../helpers/activitypub'
import { import {
isActorFollowersCountValid, isActorFollowingCountValid, isActorPreferredUsernameValid, isActorPrivateKeyValid, isActorFollowersCountValid,
isActorFollowingCountValid,
isActorPreferredUsernameValid,
isActorPrivateKeyValid,
isActorPublicKeyValid isActorPublicKeyValid
} from '../../helpers/custom-validators/activitypub/actor' } from '../../helpers/custom-validators/activitypub/actor'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'

View File

@ -1,9 +1,13 @@
import { import {
AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Is, Model, Scopes, Table, AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Is, Model, Scopes, Table,
UpdatedAt UpdatedAt, Default
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { ActivityPubActor } from '../../../shared/models/activitypub' import { ActivityPubActor } from '../../../shared/models/activitypub'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' import { VideoChannel } from '../../../shared/models/videos'
import {
isVideoChannelDescriptionValid, isVideoChannelNameValid,
isVideoChannelSupportValid
} from '../../helpers/custom-validators/video-channels'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { sendDeleteActor } from '../../lib/activitypub/send' import { sendDeleteActor } from '../../lib/activitypub/send'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
@ -67,10 +71,17 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
name: string name: string
@AllowNull(true) @AllowNull(true)
@Default(null)
@Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description')) @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description'))
@Column @Column
description: string description: string
@AllowNull(true)
@Default(null)
@Is('VideoChannelSupport', value => throwIfNotValid(value, isVideoChannelSupportValid, 'support'))
@Column
support: string
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date
@ -221,12 +232,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
.findById(id, options) .findById(id, options)
} }
toFormattedJSON () { toFormattedJSON (): VideoChannel {
const actor = this.Actor.toFormattedJSON() const actor = this.Actor.toFormattedJSON()
const account = { const account = {
id: this.id, id: this.id,
displayName: this.name, displayName: this.name,
description: this.description, description: this.description,
support: this.support,
isLocal: this.Actor.isOwned(), isLocal: this.Actor.isOwned(),
createdAt: this.createdAt, createdAt: this.createdAt,
updatedAt: this.updatedAt updatedAt: this.updatedAt
@ -240,6 +252,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
return Object.assign(obj, { return Object.assign(obj, {
summary: this.description, summary: this.description,
support: this.support,
attributedTo: [ attributedTo: [
{ {
type: 'Person' as 'Person', type: 'Person' as 'Person',

View File

@ -1,4 +1,5 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import * as Bluebird from 'bluebird'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONSTRAINTS_FIELDS } from '../../initializers' import { CONSTRAINTS_FIELDS } from '../../initializers'
@ -115,7 +116,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
.then(res => res.map(r => r.Actor)) .then(res => res.map(r => r.Actor))
} }
static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction) { static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> {
const query = { const query = {
attributes: [], attributes: [],
include: [ include: [
@ -152,4 +153,29 @@ export class VideoShareModel extends Model<VideoShareModel> {
return VideoShareModel.scope(ScopeNames.FULL).findAll(query) return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
.then(res => res.map(r => r.Actor)) .then(res => res.map(r => r.Actor))
} }
static loadActorsByVideoChannel (videoChannelId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> {
const query = {
attributes: [],
include: [
{
model: ActorModel,
required: true
},
{
attributes: [],
model: VideoModel,
required: true,
where: {
channelId: videoChannelId
}
}
],
transaction: t
}
return VideoShareModel.scope(ScopeNames.FULL)
.findAll(query)
.then(res => res.map(r => r.Actor))
}
} }

View File

@ -40,7 +40,7 @@ import {
isVideoLanguageValid, isVideoLanguageValid,
isVideoLicenceValid, isVideoLicenceValid,
isVideoNameValid, isVideoNameValid,
isVideoPrivacyValid isVideoPrivacyValid, isVideoSupportValid
} from '../../helpers/custom-validators/videos' } from '../../helpers/custom-validators/videos'
import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils' import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
@ -299,6 +299,12 @@ export class VideoModel extends Model<VideoModel> {
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max))
description: string description: string
@AllowNull(true)
@Default(null)
@Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support'))
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max))
support: string
@AllowNull(false) @AllowNull(false)
@Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration')) @Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration'))
@Column @Column
@ -841,7 +847,7 @@ export class VideoModel extends Model<VideoModel> {
return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) return join(STATIC_PATHS.PREVIEWS, this.getPreviewName())
} }
toFormattedJSON () { toFormattedJSON (): Video {
let serverHost let serverHost
if (this.VideoChannel.Account.Actor.Server) { if (this.VideoChannel.Account.Actor.Server) {
@ -875,10 +881,10 @@ export class VideoModel extends Model<VideoModel> {
embedPath: this.getEmbedPath(), embedPath: this.getEmbedPath(),
createdAt: this.createdAt, createdAt: this.createdAt,
updatedAt: this.updatedAt updatedAt: this.updatedAt
} as Video }
} }
toFormattedDetailsJSON () { toFormattedDetailsJSON (): VideoDetails {
const formattedJson = this.toFormattedJSON() const formattedJson = this.toFormattedJSON()
// Maybe our server is not up to date and there are new privacy settings since our version // Maybe our server is not up to date and there are new privacy settings since our version
@ -888,6 +894,7 @@ export class VideoModel extends Model<VideoModel> {
const detailsJson = { const detailsJson = {
privacyLabel, privacyLabel,
privacy: this.privacy, privacy: this.privacy,
support: this.support,
descriptionPath: this.getDescriptionPath(), descriptionPath: this.getDescriptionPath(),
channel: this.VideoChannel.toFormattedJSON(), channel: this.VideoChannel.toFormattedJSON(),
account: this.VideoChannel.Account.toFormattedJSON(), account: this.VideoChannel.Account.toFormattedJSON(),
@ -917,7 +924,7 @@ export class VideoModel extends Model<VideoModel> {
return -1 return -1
}) })
return Object.assign(formattedJson, detailsJson) as VideoDetails return Object.assign(formattedJson, detailsJson)
} }
toActivityPubObject (): VideoTorrentObject { toActivityPubObject (): VideoTorrentObject {
@ -957,17 +964,6 @@ export class VideoModel extends Model<VideoModel> {
let dislikesObject let dislikesObject
if (Array.isArray(this.AccountVideoRates)) { if (Array.isArray(this.AccountVideoRates)) {
const likes: string[] = []
const dislikes: string[] = []
for (const rate of this.AccountVideoRates) {
if (rate.type === 'like') {
likes.push(rate.Account.Actor.url)
} else if (rate.type === 'dislike') {
dislikes.push(rate.Account.Actor.url)
}
}
const res = this.toRatesActivityPubObjects() const res = this.toRatesActivityPubObjects()
likesObject = res.likesObject likesObject = res.likesObject
dislikesObject = res.dislikesObject dislikesObject = res.dislikesObject
@ -1032,6 +1028,7 @@ export class VideoModel extends Model<VideoModel> {
updated: this.updatedAt.toISOString(), updated: this.updatedAt.toISOString(),
mediaType: 'text/markdown', mediaType: 'text/markdown',
content: this.getTruncatedDescription(), content: this.getTruncatedDescription(),
support: this.support,
icon: { icon: {
type: 'Image', type: 'Image',
url: this.getThumbnailUrl(baseUrlHttp), url: this.getThumbnailUrl(baseUrlHttp),

View File

@ -255,6 +255,14 @@ describe('Test users API validators', function () {
await makePutBodyRequest({ url: server.url, path: path + 'me', token: 'super token', fields, statusCodeExpected: 401 }) await makePutBodyRequest({ url: server.url, path: path + 'me', token: 'super token', fields, statusCodeExpected: 401 })
}) })
it('Should fail with a too long description', async function () {
const fields = {
description: 'super'.repeat(60)
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
})
it('Should succeed with the correct params', async function () { it('Should succeed with the correct params', async function () {
const fields = { const fields = {
password: 'my super password', password: 'my super password',

View File

@ -62,7 +62,8 @@ describe('Test videos API validator', function () {
describe('When adding a video channel', function () { describe('When adding a video channel', function () {
const baseCorrectParams = { const baseCorrectParams = {
name: 'hello', name: 'hello',
description: 'super description' description: 'super description',
support: 'super support text'
} }
it('Should fail with a non authenticated user', async function () { it('Should fail with a non authenticated user', async function () {
@ -89,13 +90,18 @@ describe('Test videos API validator', function () {
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
}) })
it('Should fail with a long support text', async function () {
const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
await makePostBodyRequest({ 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 makePostBodyRequest({ await makePostBodyRequest({
url: server.url, url: server.url,
path, path,
token: server.accessToken, token: server.accessToken,
fields: baseCorrectParams, fields: baseCorrectParams,
statusCodeExpected: 204 statusCodeExpected: 200
}) })
}) })
}) })
@ -143,6 +149,11 @@ describe('Test videos API validator', function () {
await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields }) await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields })
}) })
it('Should fail with a long support text', async function () {
const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, 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

@ -102,6 +102,7 @@ describe('Test videos API validator', function () {
nsfw: false, nsfw: false,
commentsEnabled: true, commentsEnabled: true,
description: 'my super description', description: 'my super description',
support: 'my super support text',
tags: [ 'tag1', 'tag2' ], tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
channelId channelId
@ -178,7 +179,14 @@ describe('Test videos API validator', function () {
}) })
it('Should fail with a long description', async function () { it('Should fail with a long description', async function () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) })
const attaches = baseCorrectAttaches
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a long support text', async function () {
const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
const attaches = baseCorrectAttaches const attaches = baseCorrectAttaches
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@ -417,7 +425,13 @@ describe('Test videos API validator', function () {
}) })
it('Should fail with a long description', async function () { it('Should fail with a long description', async function () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) })
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
}) })

View File

@ -280,6 +280,7 @@ describe('Test follows', function () {
language: 3, language: 3,
nsfw: true, nsfw: true,
description: 'my super description', description: 'my super description',
support: 'my super support text',
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
isLocal, isLocal,

View File

@ -36,6 +36,7 @@ describe('Test handle downs', function () {
nsfw: true, nsfw: true,
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
description: 'my super description for server 1', description: 'my super description for server 1',
support: 'my super support text for server 1',
tags: [ 'tag1p1', 'tag2p1' ], tags: [ 'tag1p1', 'tag2p1' ],
fixture: 'video_short1.webm' fixture: 'video_short1.webm'
} }
@ -51,6 +52,7 @@ describe('Test handle downs', function () {
language: 9, language: 9,
nsfw: true, nsfw: true,
description: 'my super description for server 1', description: 'my super description for server 1',
support: 'my super support text for server 1',
host: 'localhost:9001', host: 'localhost:9001',
account: 'root', account: 'root',
isLocal: false, isLocal: false,

View File

@ -3,7 +3,10 @@
import * as chai from 'chai' import * as chai from 'chai'
import 'mocha' import 'mocha'
import { Account } from '../../../../shared/models/actors' import { Account } from '../../../../shared/models/actors'
import { checkVideoFilesWereRemoved, createUser, doubleFollow, flushAndRunMultipleServers, removeUser, userLogin, wait } from '../../utils' import {
checkVideoFilesWereRemoved, createUser, doubleFollow, flushAndRunMultipleServers, removeUser, updateMyUser, userLogin,
wait
} from '../../utils'
import { flushTests, getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../utils/index' import { flushTests, getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../utils/index'
import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../utils/users/accounts' import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../utils/users/accounts'
import { setAccessTokensToServers } from '../../utils/users/login' import { setAccessTokensToServers } from '../../utils/users/login'
@ -51,6 +54,22 @@ describe('Test users with multiple servers', function () {
await wait(5000) await wait(5000)
}) })
it('Should be able to update my description', async function () {
this.timeout(10000)
await updateMyUser({
url: servers[0].url,
accessToken: servers[0].accessToken,
description: 'my super description updated'
})
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
user = res.body
expect(user.account.description).to.equal('my super description updated')
await wait(5000)
})
it('Should be able to update my avatar', async function () { it('Should be able to update my avatar', async function () {
this.timeout(10000) this.timeout(10000)
@ -70,7 +89,7 @@ describe('Test users with multiple servers', function () {
await wait(5000) await wait(5000)
}) })
it('Should have updated my avatar on other servers too', async function () { it('Should have updated my avatar and my description on other servers too', async function () {
for (const server of servers) { for (const server of servers) {
const resAccounts = await getAccountsList(server.url, '-createdAt') const resAccounts = await getAccountsList(server.url, '-createdAt')
@ -81,6 +100,7 @@ describe('Test users with multiple servers', function () {
const rootServer1Get = resAccount.body as Account const rootServer1Get = resAccount.body as Account
expect(rootServer1Get.name).to.equal('root') expect(rootServer1Get.name).to.equal('root')
expect(rootServer1Get.host).to.equal('localhost:9001') expect(rootServer1Get.host).to.equal('localhost:9001')
expect(rootServer1Get.description).to.equal('my super description updated')
await testImage(server.url, 'avatar2-resized', rootServer1Get.avatar.path, '.png') await testImage(server.url, 'avatar2-resized', rootServer1Get.avatar.path, '.png')
} }

View File

@ -172,6 +172,7 @@ describe('Test users', function () {
expect(user.videoQuota).to.equal(2 * 1024 * 1024) expect(user.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.roleLabel).to.equal('User') expect(user.roleLabel).to.equal('User')
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.be.null
}) })
it('Should be able to upload a video with this user', async function () { it('Should be able to upload a video with this user', async function () {
@ -315,6 +316,7 @@ describe('Test users', function () {
expect(user.displayNSFW).to.be.ok expect(user.displayNSFW).to.be.ok
expect(user.videoQuota).to.equal(2 * 1024 * 1024) expect(user.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.be.null
}) })
it('Should be able to change the autoPlayVideo attribute', async function () { it('Should be able to change the autoPlayVideo attribute', async function () {
@ -345,6 +347,7 @@ describe('Test users', function () {
expect(user.displayNSFW).to.be.ok expect(user.displayNSFW).to.be.ok
expect(user.videoQuota).to.equal(2 * 1024 * 1024) expect(user.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.be.null
}) })
it('Should be able to update my avatar', async function () { it('Should be able to update my avatar', async function () {
@ -362,6 +365,24 @@ describe('Test users', function () {
await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png') await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png')
}) })
it('Should be able to update my description', async function () {
await updateMyUser({
url: server.url,
accessToken: accessTokenUser,
description: 'my super description updated'
})
const res = await getMyUserInformation(server.url, accessTokenUser)
const user = res.body
expect(user.username).to.equal('user_1')
expect(user.email).to.equal('updated@example.com')
expect(user.displayNSFW).to.be.ok
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number')
expect(user.account.description).to.equal('my super description updated')
})
it('Should be able to update another user', async function () { it('Should be able to update another user', async function () {
await updateUser({ await updateUser({
url: server.url, url: server.url,

View File

@ -70,6 +70,7 @@ describe('Test multiple servers', function () {
language: 9, language: 9,
nsfw: true, nsfw: true,
description: 'my super description for server 1', description: 'my super description for server 1',
support: 'my super support text for server 1',
tags: [ 'tag1p1', 'tag2p1' ], tags: [ 'tag1p1', 'tag2p1' ],
channelId: videoChannelId, channelId: videoChannelId,
fixture: 'video_short1.webm' fixture: 'video_short1.webm'
@ -88,6 +89,7 @@ describe('Test multiple servers', function () {
language: 9, language: 9,
nsfw: true, nsfw: true,
description: 'my super description for server 1', description: 'my super description for server 1',
support: 'my super support text for server 1',
host: 'localhost:9001', host: 'localhost:9001',
account: 'root', account: 'root',
isLocal, isLocal,
@ -136,6 +138,7 @@ describe('Test multiple servers', function () {
language: 11, language: 11,
nsfw: true, nsfw: true,
description: 'my super description for server 2', description: 'my super description for server 2',
support: 'my super support text for server 2',
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
fixture: 'video_short2.webm', fixture: 'video_short2.webm',
thumbnailfile: 'thumbnail.jpg', thumbnailfile: 'thumbnail.jpg',
@ -156,6 +159,7 @@ describe('Test multiple servers', function () {
language: 11, language: 11,
nsfw: true, nsfw: true,
description: 'my super description for server 2', description: 'my super description for server 2',
support: 'my super support text for server 2',
host: 'localhost:9002', host: 'localhost:9002',
account: 'user1', account: 'user1',
isLocal, isLocal,
@ -211,6 +215,7 @@ describe('Test multiple servers', function () {
language: 11, language: 11,
nsfw: true, nsfw: true,
description: 'my super description for server 3', description: 'my super description for server 3',
support: 'my super support text for server 3',
tags: [ 'tag1p3' ], tags: [ 'tag1p3' ],
fixture: 'video_short3.webm' fixture: 'video_short3.webm'
} }
@ -223,6 +228,7 @@ describe('Test multiple servers', function () {
language: 12, language: 12,
nsfw: false, nsfw: false,
description: 'my super description for server 3-2', description: 'my super description for server 3-2',
support: 'my super support text for server 3-2',
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
fixture: 'video_short.webm' fixture: 'video_short.webm'
} }
@ -257,6 +263,7 @@ describe('Test multiple servers', function () {
language: 11, language: 11,
nsfw: true, nsfw: true,
description: 'my super description for server 3', description: 'my super description for server 3',
support: 'my super support text for server 3',
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
isLocal, isLocal,
@ -286,6 +293,7 @@ describe('Test multiple servers', function () {
language: 12, language: 12,
nsfw: false, nsfw: false,
description: 'my super description for server 3-2', description: 'my super description for server 3-2',
support: 'my super support text for server 3-2',
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
commentsEnabled: true, commentsEnabled: true,
@ -525,6 +533,7 @@ describe('Test multiple servers', function () {
language: 13, language: 13,
nsfw: true, nsfw: true,
description: 'my super description updated', description: 'my super description updated',
support: 'my super support text updated',
tags: [ 'tag_up_1', 'tag_up_2' ], tags: [ 'tag_up_1', 'tag_up_2' ],
thumbnailfile: 'thumbnail.jpg', thumbnailfile: 'thumbnail.jpg',
previewfile: 'preview.jpg' previewfile: 'preview.jpg'
@ -553,6 +562,7 @@ describe('Test multiple servers', function () {
language: 13, language: 13,
nsfw: true, nsfw: true,
description: 'my super description updated', description: 'my super description updated',
support: 'my super support text updated',
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
isLocal, isLocal,
@ -841,6 +851,7 @@ describe('Test multiple servers', function () {
language: null, language: null,
nsfw: false, nsfw: false,
description: null, description: null,
support: null,
host: 'localhost:9002', host: 'localhost:9002',
account: 'root', account: 'root',
isLocal, isLocal,

View File

@ -26,6 +26,7 @@ describe('Test a single server', function () {
language: 3, language: 3,
nsfw: true, nsfw: true,
description: 'my super description', description: 'my super description',
support: 'my super support text',
host: 'localhost:9001', host: 'localhost:9001',
account: 'root', account: 'root',
isLocal: true, isLocal: true,
@ -54,6 +55,7 @@ describe('Test a single server', function () {
language: 5, language: 5,
nsfw: false, nsfw: false,
description: 'my super description updated', description: 'my super description updated',
support: 'my super support text updated',
host: 'localhost:9001', host: 'localhost:9001',
account: 'root', account: 'root',
isLocal: true, isLocal: true,

View File

@ -1,27 +1,27 @@
/* tslint:disable:no-unused-expression */ /* tslint:disable:no-unused-expression */
import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import 'mocha'
import { User } from '../../../../shared/index'
import { doubleFollow, flushAndRunMultipleServers, uploadVideo, wait } from '../../utils'
import {
addVideoChannel,
deleteVideoChannel,
flushTests,
getAccountVideoChannelsList,
getMyUserInformation,
getVideoChannel,
getVideoChannelsList,
killallServers,
ServerInfo,
setAccessTokensToServers,
updateVideoChannel
} from '../../utils/index'
const expect = chai.expect const expect = chai.expect
import { describe('Test video channels', function () {
ServerInfo, let servers: ServerInfo[]
flushTests,
runServer,
setAccessTokensToServers,
killallServers,
getMyUserInformation,
getVideoChannelsList,
addVideoChannel,
getAccountVideoChannelsList,
updateVideoChannel,
deleteVideoChannel,
getVideoChannel
} from '../../utils/index'
import { User } from '../../../../shared/index'
describe('Test a video channels', function () {
let server: ServerInfo
let userInfo: User let userInfo: User
let videoChannelId: number let videoChannelId: number
@ -30,29 +30,41 @@ describe('Test a video channels', function () {
await flushTests() await flushTests()
server = await runServer(1) servers = await flushAndRunMultipleServers(2)
await setAccessTokensToServers([ server ]) await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
await wait(5000)
}) })
it('Should have one video channel (created with root)', async () => { it('Should have one video channel (created with root)', async () => {
const res = await getVideoChannelsList(server.url, 0, 2) const res = await getVideoChannelsList(servers[0].url, 0, 2)
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(1) expect(res.body.data).to.have.lengthOf(1)
}) })
it('Should create another video channel', async () => { it('Should create another video channel', async function () {
this.timeout(10000)
const videoChannel = { const videoChannel = {
name: 'second video channel', name: 'second video channel',
description: 'super video channel description' description: 'super video channel description',
support: 'super video channel support text'
} }
await addVideoChannel(server.url, server.accessToken, videoChannel) const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
videoChannelId = res.body.videoChannel.id
// The channel is 1 is propagated to servers 2
await uploadVideo(servers[0].url, servers[0].accessToken, { channelId: videoChannelId })
await wait(3000)
}) })
it('Should have two video channels when getting my information', async () => { it('Should have two video channels when getting my information', async () => {
const res = await getMyUserInformation(server.url, server.accessToken) const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
userInfo = res.body userInfo = res.body
expect(userInfo.videoChannels).to.be.an('array') expect(userInfo.videoChannels).to.be.an('array')
@ -62,11 +74,11 @@ describe('Test a video channels', function () {
expect(videoChannels[0].displayName).to.equal('Default root channel') expect(videoChannels[0].displayName).to.equal('Default root channel')
expect(videoChannels[1].displayName).to.equal('second video channel') expect(videoChannels[1].displayName).to.equal('second video channel')
expect(videoChannels[1].description).to.equal('super video channel description') expect(videoChannels[1].description).to.equal('super video channel description')
expect(videoChannels[1].support).to.equal('super video channel support text')
}) })
it('Should have two video channels when getting account channels', async () => { it('Should have two video channels when getting account channels on server 1', async function () {
const res = await getAccountVideoChannelsList(server.url, userInfo.account.uuid) const res = await getAccountVideoChannelsList(servers[0].url, userInfo.account.uuid)
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(2) expect(res.body.data).to.have.lengthOf(2)
@ -75,12 +87,23 @@ describe('Test a video channels', function () {
expect(videoChannels[0].displayName).to.equal('Default root channel') expect(videoChannels[0].displayName).to.equal('Default root channel')
expect(videoChannels[1].displayName).to.equal('second video channel') expect(videoChannels[1].displayName).to.equal('second video channel')
expect(videoChannels[1].description).to.equal('super video channel description') expect(videoChannels[1].description).to.equal('super video channel description')
expect(videoChannels[1].support).to.equal('super video channel support text')
videoChannelId = videoChannels[1].id
}) })
it('Should list video channels', async () => { it('Should have one video channel when getting account channels on server 2', async function () {
const res = await getVideoChannelsList(server.url, 1, 1, '-name') const res = await getAccountVideoChannelsList(servers[1].url, userInfo.account.uuid)
expect(res.body.total).to.equal(1)
expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(1)
const videoChannels = res.body.data
expect(videoChannels[0].displayName).to.equal('second video channel')
expect(videoChannels[0].description).to.equal('super video channel description')
expect(videoChannels[0].support).to.equal('super video channel support text')
})
it('Should list video channels', async function () {
const res = await getVideoChannelsList(servers[0].url, 1, 1, '-name')
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
@ -88,39 +111,48 @@ describe('Test a video channels', function () {
expect(res.body.data[0].displayName).to.equal('Default root channel') expect(res.body.data[0].displayName).to.equal('Default root channel')
}) })
it('Should update video channel', async () => { it('Should update video channel', async function () {
this.timeout(5000)
const videoChannelAttributes = { const videoChannelAttributes = {
name: 'video channel updated', name: 'video channel updated',
description: 'video channel description updated' description: 'video channel description updated',
support: 'video channel support text updated'
} }
await updateVideoChannel(server.url, server.accessToken, videoChannelId, videoChannelAttributes) await updateVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId, videoChannelAttributes)
await wait(3000)
}) })
it('Should have video channel updated', async () => { it('Should have video channel updated', async function () {
const res = await getVideoChannelsList(server.url, 0, 1, '-name') for (const server of servers) {
const res = await getVideoChannelsList(server.url, 0, 1, '-name')
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(1) expect(res.body.data).to.have.lengthOf(1)
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')
}
}) })
it('Should get video channel', async () => { it('Should get video channel', async function () {
const res = await getVideoChannel(server.url, videoChannelId) const res = await getVideoChannel(servers[0].url, videoChannelId)
const videoChannel = res.body const videoChannel = res.body
expect(videoChannel.displayName).to.equal('video channel updated') expect(videoChannel.displayName).to.equal('video channel updated')
expect(videoChannel.description).to.equal('video channel description updated') expect(videoChannel.description).to.equal('video channel description updated')
expect(videoChannel.support).to.equal('video channel support text updated')
}) })
it('Should delete video channel', async () => { it('Should delete video channel', async function () {
await deleteVideoChannel(server.url, server.accessToken, videoChannelId) await deleteVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId)
}) })
it('Should have video channel deleted', async () => { it('Should have video channel deleted', async function () {
const res = await getVideoChannelsList(server.url, 0, 10) const res = await getVideoChannelsList(servers[0].url, 0, 10)
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
@ -129,7 +161,7 @@ describe('Test a video channels', function () {
}) })
after(async function () { after(async function () {
killallServers([ server ]) killallServers(servers)
// Keep the logs if the test failed // Keep the logs if the test failed
if (this['ok']) { if (this['ok']) {

View File

@ -131,6 +131,7 @@ function updateMyUser (options: {
displayNSFW?: boolean, displayNSFW?: boolean,
email?: string, email?: string,
autoPlayVideo?: boolean autoPlayVideo?: boolean
description?: string
}) { }) {
const path = '/api/v1/users/me' const path = '/api/v1/users/me'
@ -139,6 +140,7 @@ function updateMyUser (options: {
if (options.displayNSFW !== undefined && options.displayNSFW !== null) toSend['displayNSFW'] = options.displayNSFW if (options.displayNSFW !== undefined && options.displayNSFW !== null) toSend['displayNSFW'] = options.displayNSFW
if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
return makePutBodyRequest({ return makePutBodyRequest({
url: options.url, url: options.url,

View File

@ -3,6 +3,7 @@ import * as request from 'supertest'
type VideoChannelAttributes = { type VideoChannelAttributes = {
name?: string name?: string
description?: string description?: string
support?: string
} }
function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
@ -30,13 +31,14 @@ function getAccountVideoChannelsList (url: string, accountId: number | string, s
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
} }
function addVideoChannel (url: string, token: string, videoChannelAttributesArg: VideoChannelAttributes, expectedStatus = 204) { function addVideoChannel (url: string, token: string, videoChannelAttributesArg: VideoChannelAttributes, expectedStatus = 200) {
const path = '/api/v1/videos/channels' const path = '/api/v1/videos/channels'
// Default attributes // Default attributes
let attributes = { let attributes = {
name: 'my super video channel', name: 'my super video channel',
description: 'my super channel description' description: 'my super channel description',
support: 'my super channel support'
} }
attributes = Object.assign(attributes, videoChannelAttributesArg) attributes = Object.assign(attributes, videoChannelAttributesArg)
@ -54,6 +56,7 @@ function updateVideoChannel (url: string, token: string, channelId: number, attr
if (attributes.name) body['name'] = attributes.name if (attributes.name) body['name'] = attributes.name
if (attributes.description) body['description'] = attributes.description if (attributes.description) body['description'] = attributes.description
if (attributes.support) body['support'] = attributes.support
return request(url) return request(url)
.put(path) .put(path)

View File

@ -248,6 +248,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
channelId: defaultChannelId, channelId: defaultChannelId,
nsfw: true, nsfw: true,
description: 'my super description', description: 'my super description',
support: 'my super support text',
tags: [ 'tag' ], tags: [ 'tag' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true, commentsEnabled: true,
@ -277,6 +278,10 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
req.field('licence', attributes.licence.toString()) 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) { if (attributes.thumbnailfile !== undefined) {
req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile)) req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
} }
@ -284,10 +289,6 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile)) req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
} }
for (let i = 0; i < attributes.tags.length; i++) {
req.field('tags[' + i + ']', attributes.tags[i])
}
return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
.expect(specialStatus) .expect(specialStatus)
} }
@ -366,6 +367,7 @@ async function completeVideoCheck (
nsfw: boolean nsfw: boolean
commentsEnabled: boolean commentsEnabled: boolean
description: string description: string
support: string
host: string host: string
account: string account: string
isLocal: boolean, isLocal: boolean,

View File

@ -19,6 +19,7 @@ program
.option('-L, --language <language number>', 'Language number') .option('-L, --language <language number>', 'Language number')
.option('-d, --video-description <description>', 'Video description') .option('-d, --video-description <description>', 'Video description')
.option('-t, --tags <tags>', 'Video tags', list) .option('-t, --tags <tags>', 'Video tags', list)
.option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
.option('-f, --file <file>', 'Video absolute file path') .option('-f, --file <file>', 'Video absolute file path')
.parse(process.argv) .parse(process.argv)
@ -72,7 +73,8 @@ async function run () {
description: program['videoDescription'], description: program['videoDescription'],
tags: program['tags'], tags: program['tags'],
commentsEnabled: program['commentsEnabled'], commentsEnabled: program['commentsEnabled'],
fixture: program['file'] fixture: program['file'],
thumbnailfile: program['thumbnailPath']
} }
await uploadVideo(program['url'], accessToken, videoAttributes) await uploadVideo(program['url'], accessToken, videoAttributes)

View File

@ -19,6 +19,7 @@ export interface ActivityPubActor {
summary: string summary: string
attributedTo: ActivityPubAttributedTo[] attributedTo: ActivityPubAttributedTo[]
support?: string
uuid: string uuid: string
publicKey: { publicKey: {
id: string id: string
@ -26,11 +27,9 @@ export interface ActivityPubActor {
publicKeyPem: string publicKeyPem: string
} }
// Not used
icon: { icon: {
type: 'Image' type: 'Image'
mediaType: 'image/png' mediaType: 'image/png'
url: string url: string
} }
// liked: string
} }

View File

@ -23,6 +23,7 @@ export interface VideoTorrentObject {
updated: string updated: string
mediaType: 'text/markdown' mediaType: 'text/markdown'
content: string content: string
support: string
icon: ActivityIconObject icon: ActivityIconObject
url: ActivityUrlObject[] url: ActivityUrlObject[]
likes?: ActivityPubOrderedCollection<string> likes?: ActivityPubOrderedCollection<string>

View File

@ -2,4 +2,5 @@ import { Actor } from './actor.model'
export interface Account extends Actor { export interface Account extends Actor {
displayName: string displayName: string
description: string
} }

View File

@ -1,4 +1,5 @@
export interface UserUpdateMe { export interface UserUpdateMe {
description?: string
displayNSFW?: boolean displayNSFW?: boolean
autoPlayVideo?: boolean autoPlayVideo?: boolean
email?: string email?: string

View File

@ -1,4 +1,5 @@
export interface VideoChannelCreate { export interface VideoChannelCreate {
name: string name: string
description?: string description?: string
support?: string
} }

View File

@ -1,4 +1,5 @@
export interface VideoChannelUpdate { export interface VideoChannelUpdate {
name: string name: string
description: string description?: string
support?: string
} }

View File

@ -4,6 +4,7 @@ import { Video } from './video.model'
export interface VideoChannel extends Actor { export interface VideoChannel extends Actor {
displayName: string displayName: string
description: string description: string
support: string
isLocal: boolean isLocal: boolean
owner?: { owner?: {
name: string name: string

View File

@ -5,6 +5,7 @@ export interface VideoCreate {
licence?: number licence?: number
language?: number language?: number
description?: string description?: string
support?: string
channelId: number channelId: number
nsfw: boolean nsfw: boolean
name: string name: string

View File

@ -6,6 +6,7 @@ export interface VideoUpdate {
licence?: number licence?: number
language?: number language?: number
description?: string description?: string
support?: string
privacy?: VideoPrivacy privacy?: VideoPrivacy
tags?: string[] tags?: string[]
commentsEnabled?: boolean commentsEnabled?: boolean

View File

@ -41,6 +41,7 @@ export interface VideoDetails extends Video {
privacy: VideoPrivacy privacy: VideoPrivacy
privacyLabel: string privacyLabel: string
descriptionPath: string descriptionPath: string
support: string
channel: VideoChannel channel: VideoChannel
tags: string[] tags: string[]
files: VideoFile[] files: VideoFile[]