Implement support field in video and video channel
authorChocobozzz <me@florianbigard.com>
Thu, 15 Feb 2018 13:46:26 +0000 (14:46 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 15 Feb 2018 14:29:07 +0000 (15:29 +0100)
49 files changed:
client/src/app/shared/account/account.model.ts
client/src/app/shared/video/video-details.model.ts
server/controllers/api/users.ts
server/controllers/api/videos/channel.ts
server/controllers/api/videos/index.ts
server/helpers/activitypub.ts
server/helpers/custom-validators/accounts.ts
server/helpers/custom-validators/users.ts
server/helpers/custom-validators/video-channels.ts
server/helpers/custom-validators/videos.ts
server/initializers/constants.ts
server/initializers/migrations/0195-support.ts [new file with mode: 0644]
server/lib/activitypub/actor.ts
server/lib/activitypub/process/process-update.ts
server/lib/activitypub/send/send-update.ts
server/lib/activitypub/videos.ts
server/lib/video-channel.ts
server/middlewares/validators/users.ts
server/middlewares/validators/video-channels.ts
server/middlewares/validators/videos.ts
server/models/account/account.ts
server/models/activitypub/actor.ts
server/models/video/video-channel.ts
server/models/video/video-share.ts
server/models/video/video.ts
server/tests/api/check-params/users.ts
server/tests/api/check-params/video-channels.ts
server/tests/api/check-params/videos.ts
server/tests/api/server/follows.ts
server/tests/api/server/handle-down.ts
server/tests/api/users/users-multiple-servers.ts
server/tests/api/users/users.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/single-server.ts
server/tests/api/videos/video-channels.ts
server/tests/utils/users/users.ts
server/tests/utils/videos/video-channels.ts
server/tests/utils/videos/videos.ts
server/tools/upload.ts
shared/models/activitypub/activitypub-actor.ts
shared/models/activitypub/objects/video-torrent-object.ts
shared/models/actors/account.model.ts
shared/models/users/user-update-me.model.ts
shared/models/videos/video-channel-create.model.ts
shared/models/videos/video-channel-update.model.ts
shared/models/videos/video-channel.model.ts
shared/models/videos/video-create.model.ts
shared/models/videos/video-update.model.ts
shared/models/videos/video.model.ts

index dffca783b159f94500e5055aee1ad948904087ee..0bdc76478264fc3743fd249df37e048f2b38e162 100644 (file)
@@ -8,6 +8,7 @@ export class Account implements ServerAccount {
   url: string
   name: string
   displayName: string
+  description: string
   host: string
   followingCount: number
   followersCount: number
index cf6b71b608c33c7c019152a61c1a813dba88b8dc..c746bfd6630d6e533c8678b1900f484fcbdebb74 100644 (file)
@@ -18,6 +18,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   languageLabel: string
   language: number
   description: string
+  support: string
   duration: number
   durationLabel: string
   id: number
index e3067584e7edabf7a082cf26a8fe14bce49b2047..583376c3839517d75cfae2a06b668a8f55b59871 100644 (file)
@@ -9,7 +9,7 @@ import { logger } from '../../helpers/logger'
 import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
 import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
 import { updateActorAvatarInstance } from '../../lib/activitypub'
-import { sendUpdateUser } from '../../lib/activitypub/send'
+import { sendUpdateActor } from '../../lib/activitypub/send'
 import { Emailer } from '../../lib/emailer'
 import { Redis } from '../../lib/redis'
 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) {
   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.email !== undefined) user.email = body.email
   if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
   if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
 
-  await user.save()
-  await sendUpdateUser(user, undefined)
+  await sequelizeTypescript.transaction(async t => {
+    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)
 }
@@ -297,7 +303,7 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
     const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
     await updatedActor.save({ transaction: t })
 
-    await sendUpdateUser(user, t)
+    await sendUpdateActor(user.Account, t)
 
     return updatedActor.Avatar
   })
index 8ec53d9ae1b3991d502e792a20c72b74a3a66afd..fba5681de79689b143d2c8a01efe770a185caaaa 100644 (file)
@@ -5,6 +5,7 @@ import { logger } from '../../../helpers/logger'
 import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils'
 import { sequelizeTypescript } from '../../../initializers'
 import { setAsyncActorKeys } from '../../../lib/activitypub'
+import { sendUpdateActor } from '../../../lib/activitypub/send'
 import { createVideoChannel } from '../../../lib/video-channel'
 import {
   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.'
   }
 
-  await retryTransactionWrapper(addVideoChannel, options)
-
-  // TODO : include Location of the new video channel -> 201
-  return res.type('json').status(204).end()
+  const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
+  return res.json({
+    videoChannel: {
+      id: videoChannel.id
+    }
+  }).end()
 }
 
 async function addVideoChannel (req: express.Request, res: express.Response) {
   const videoChannelInfo: VideoChannelCreate = req.body
   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)
   })
 
   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)
+
+  return videoChannelCreated
 }
 
 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.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
+      if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
 
-      await videoChannelInstance.save(sequelizeOptions)
-
-      // TODO
-      // await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
+      const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
+      await sendUpdateActor(videoChannelInstanceUpdated, t)
     })
 
     logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
index 564ccd3f8d7fc01b4b70bddd3b793a3b766053d9..c9334676e7ee982e807dd335fcd056534a16282c 100644 (file)
@@ -178,6 +178,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
     commentsEnabled: videoInfo.commentsEnabled,
     nsfw: videoInfo.nsfw,
     description: videoInfo.description,
+    support: videoInfo.support,
     privacy: videoInfo.privacy,
     duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
     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.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
       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.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
 
index eaee324eb1e5fe38102033a17847ed579f075728..d64a6dd78fd6bb849e92952415303ff591756a3a 100644 (file)
@@ -19,7 +19,8 @@ function activityPubContextify <T> (data: T) {
         'language': 'http://schema.org/inLanguage',
         'views': '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: {
index 8dc5d1f0d778fa7f2623d85a9861beeb3f4842d5..a46ffc1622ea2403eb6218eb33ed79712b427ea7 100644 (file)
@@ -3,12 +3,16 @@ import { Response } from 'express'
 import 'express-validator'
 import * as validator from 'validator'
 import { AccountModel } from '../../models/account/account'
-import { isUserUsernameValid } from './users'
+import { isUserDescriptionValid, isUserUsernameValid } from './users'
 
 function isAccountNameValid (value: string) {
   return isUserUsernameValid(value)
 }
 
+function isAccountDescriptionValid (value: string) {
+  return isUserDescriptionValid(value)
+}
+
 function isAccountIdExist (id: number | string, res: Response) {
   let promise: Bluebird<AccountModel>
 
@@ -48,5 +52,6 @@ async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
 export {
   isAccountIdExist,
   isLocalAccountNameExist,
+  isAccountDescriptionValid,
   isAccountNameValid
 }
index e805313f831684b5ae2d94507d1f821a2df5584e..bbc7cc1994e31ac7dbe463ac1fc95381e3f1d103 100644 (file)
@@ -21,6 +21,10 @@ function isUserUsernameValid (value: string) {
   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) {
   return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
 }
@@ -54,5 +58,6 @@ export {
   isUserUsernameValid,
   isUserDisplayNSFWValid,
   isUserAutoPlayVideoValid,
+  isUserDescriptionValid,
   isAvatarFile
 }
index 6bc96bf51bbdb3d0028c494217b0281023b533a0..2a6f56840c4e8fe9e76345b4bf3c579a3a2a352b 100644 (file)
@@ -16,6 +16,10 @@ function isVideoChannelNameValid (value: string) {
   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) {
   let videoChannel: VideoChannelModel
   if (validator.isInt(id)) {
@@ -41,5 +45,6 @@ async function isVideoChannelExist (id: string, res: express.Response) {
 export {
   isVideoChannelDescriptionValid,
   isVideoChannelNameValid,
+  isVideoChannelSupportValid,
   isVideoChannelExist
 }
index 8ef3a3c643895b67fb1daed22de2b9faf7ea7996..a46d715ba8cf89d90b6ef4a69d9a48297ddf7d32 100644 (file)
@@ -42,6 +42,10 @@ function isVideoDescriptionValid (value: string) {
   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) {
   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
 }
@@ -140,5 +144,6 @@ export {
   isVideoFileResolutionValid,
   isVideoFileSizeValid,
   isVideoExist,
-  isVideoImage
+  isVideoImage,
+  isVideoSupportValid
 }
index 91fbbde75e4bbc590dcb750f9b466b3f2423cef8..ac001bbc7cb06c2aadd52614dc6032623702e112 100644 (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: {
     USERNAME: { min: 3, max: 20 }, // Length
     PASSWORD: { min: 6, max: 255 }, // Length
+    DESCRIPTION: { min: 3, max: 250 }, // Length
     VIDEO_QUOTA: { min: -1 }
   },
   VIDEO_ABUSES: {
@@ -176,12 +177,14 @@ const CONSTRAINTS_FIELDS = {
   VIDEO_CHANNELS: {
     NAME: { min: 3, max: 120 }, // Length
     DESCRIPTION: { min: 3, max: 250 }, // Length
+    SUPPORT: { min: 3, max: 300 }, // Length
     URL: { min: 3, max: 2000 } // Length
   },
   VIDEOS: {
     NAME: { min: 3, max: 120 }, // 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: {
       EXTNAME: [ '.jpg', '.jpeg' ],
       FILE_SIZE: {
diff --git a/server/initializers/migrations/0195-support.ts b/server/initializers/migrations/0195-support.ts
new file mode 100644 (file)
index 0000000..8722a5f
--- /dev/null
@@ -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
+}
index c3255d8ca1ac408e95b0b07b73626969abbda005..897acee8598c2acc52b0aad4fe041c8b345375e7 100644 (file)
@@ -225,12 +225,10 @@ function saveActorAndServerAndModelIfNotExist (
     })
 
     if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
-      const account = await saveAccount(actorCreated, result, t)
-      actorCreated.Account = account
+      actorCreated.Account = await saveAccount(actorCreated, result, t)
       actorCreated.Account.Actor = actorCreated
     } else if (actorCreated.type === 'Group') { // Video channel
-      const videoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
-      actorCreated.VideoChannel = videoChannel
+      actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
       actorCreated.VideoChannel.Actor = actorCreated
     }
 
@@ -242,6 +240,7 @@ type FetchRemoteActorResult = {
   actor: ActorModel
   name: string
   summary: string
+  support?: string
   avatarName?: string
   attributedTo: ActivityPubAttributedTo[]
 }
@@ -290,6 +289,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
     name,
     avatarName,
     summary: actorJSON.summary,
+    support: actorJSON.support,
     attributedTo: actorJSON.attributedTo
   }
 }
@@ -298,6 +298,7 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t
   const [ accountCreated ] = await AccountModel.findOrCreate({
     defaults: {
       name: result.name,
+      description: result.summary,
       actorId: actor.id
     },
     where: {
@@ -314,6 +315,7 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
     defaults: {
       name: result.name,
       description: result.summary,
+      support: result.support,
       actorId: actor.id,
       accountId: ownerActor.Account.id
     },
@@ -352,11 +354,14 @@ async function refreshActorIfNeeded (actor: ActorModel) {
         await actor.save({ transaction: t })
 
         actor.Account.set('name', result.name)
+        actor.Account.set('description', result.summary)
         await actor.Account.save({ transaction: t })
       } else if (actor.VideoChannel) {
         await actor.save({ transaction: t })
 
         actor.VideoChannel.set('name', result.name)
+        actor.VideoChannel.set('description', result.summary)
+        actor.VideoChannel.set('support', result.support)
         await actor.VideoChannel.save({ transaction: t })
       }
 
index c7ad412bc37b9f5eae78f3df4abd634c19ee4483..566e5938b3b918707fe058dfb9435dfa62e61e19 100644 (file)
@@ -9,20 +9,24 @@ import { sequelizeTypescript } from '../../../initializers'
 import { AccountModel } from '../../../models/account/account'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { TagModel } from '../../../models/video/tag'
+import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoFileModel } from '../../../models/video/video-file'
 import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
 import {
-  generateThumbnailFromUrl, getOrCreateAccountAndVideoAndChannel, videoActivityObjectToDBAttributes,
+  generateThumbnailFromUrl,
+  getOrCreateAccountAndVideoAndChannel,
+  videoActivityObjectToDBAttributes,
   videoFileActivityUrlToDBAttributes
 } from '../videos'
 
 async function processUpdateActivity (activity: ActivityUpdate) {
   const actor = await getOrCreateActorAndServerAndModel(activity.actor)
+  const objectType = activity.object.type
 
-  if (activity.object.type === 'Video') {
+  if (objectType === 'Video') {
     return processUpdateVideo(actor, activity)
-  } else if (activity.object.type === 'Person') {
-    return processUpdateAccount(actor, activity)
+  } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
+    return processUpdateActor(actor, activity)
   }
 
   return
@@ -75,6 +79,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
       videoInstance.set('licence', videoData.licence)
       videoInstance.set('language', videoData.language)
       videoInstance.set('description', videoData.description)
+      videoInstance.set('support', videoData.support)
       videoInstance.set('nsfw', videoData.nsfw)
       videoInstance.set('commentsEnabled', videoData.commentsEnabled)
       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 = {
     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) {
-  const accountAttributesToUpdate = activity.object as ActivityPubActor
+async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
+  const actorAttributesToUpdate = activity.object as ActivityPubActor
 
-  logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
-  let accountInstance: AccountModel
+  logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
+  let accountOrChannelInstance: AccountModel | VideoChannelModel
   let actorFieldsSave: object
-  let accountFieldsSave: object
+  let accountOrChannelFieldsSave: object
 
   // Fetch icon?
-  const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate)
+  const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
 
   try {
     await sequelizeTypescript.transaction(async t => {
       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) {
         await updateActorAvatarInstance(actor, avatarName, t)
@@ -151,18 +159,20 @@ async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate)
 
       await actor.save({ transaction: t })
 
-      actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername)
-      await actor.Account.save({ transaction: t })
+      accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
+      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) {
     if (actor !== undefined && actorFieldsSave !== undefined) {
       resetSequelizeInstance(actor, actorFieldsSave)
     }
 
-    if (accountInstance !== undefined && accountFieldsSave !== undefined) {
-      resetSequelizeInstance(accountInstance, accountFieldsSave)
+    if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
+      resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
     }
 
     // This is just a debug because we will retry the insert
index e8f11edd0ec2a21b32bc2e0498774acb8d61ace8..62264830879f5949fe41442ccbdf031a3e1e3e0f 100644 (file)
@@ -1,9 +1,10 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
 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 { VideoModel } from '../../../models/video/video'
+import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { getUpdateActivityPubUrl } from '../url'
 import { audiencify, broadcastToFollowers, getAudience } from './misc'
@@ -23,15 +24,23 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) {
   return broadcastToFollowers(data, byActor, actorsInvolved, t)
 }
 
-async function sendUpdateUser (user: UserModel, t: Transaction) {
-  const byActor = user.Account.Actor
+async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) {
+  const byActor = accountOrChannel.Actor
 
   const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString())
-  const accountObject = user.Account.toActivityPubObject()
+  const accountOrChannelObject = accountOrChannel.toActivityPubObject()
   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)
 
   return broadcastToFollowers(data, byActor, actorsInvolved, t)
@@ -40,7 +49,7 @@ async function sendUpdateUser (user: UserModel, t: Transaction) {
 // ---------------------------------------------------------------------------
 
 export {
-  sendUpdateUser,
+  sendUpdateActor,
   sendUpdateVideo
 }
 
index 40e9318e3817cd0234d2e344783bd893a095acf5..e65362190cb481814a936070b853af34c3aab7f1 100644 (file)
@@ -83,6 +83,11 @@ async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelMode
     description = videoObject.content
   }
 
+  let support = null
+  if (videoObject.support) {
+    support = videoObject.support
+  }
+
   return {
     name: videoObject.name,
     uuid: videoObject.uuid,
@@ -91,6 +96,7 @@ async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelMode
     licence,
     language,
     description,
+    support,
     nsfw: videoObject.sensitive,
     commentsEnabled: videoObject.commentsEnabled,
     channelId: videoChannel.id,
index 569b8f29d79667203ccdb3b37ad55347c2b06603..9f7ed929738c56d7f915cb59d70279b5afd6debd 100644 (file)
@@ -16,6 +16,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
   const videoChannelData = {
     name: videoChannelInfo.name,
     description: videoChannelInfo.description,
+    support: videoChannelInfo.support,
     accountId: account.id,
     actorId: actorInstanceCreated.id
   }
index cba15c8d3c33e11fe1a7c28be15cdfaf9ae2fa1a..49bc0bb560edb85f5c740b6fe69d8e8b3752c397 100644 (file)
@@ -7,6 +7,7 @@ import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
   isAvatarFile,
   isUserAutoPlayVideoValid,
+  isUserDescriptionValid,
   isUserDisplayNSFWValid,
   isUserPasswordValid,
   isUserRoleValid,
@@ -97,6 +98,7 @@ const usersUpdateValidator = [
 ]
 
 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('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'),
index 86bddde8268dbe4085b919031ac44dff0688662c..fe42105e871f35b84169b8b54834a4c6e0ff34eb 100644 (file)
@@ -5,7 +5,7 @@ import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
   isVideoChannelDescriptionValid, isVideoChannelExist,
-  isVideoChannelNameValid
+  isVideoChannelNameValid, isVideoChannelSupportValid
 } from '../../helpers/custom-validators/video-channels'
 import { logger } from '../../helpers/logger'
 import { UserModel } from '../../models/account/user'
@@ -27,7 +27,8 @@ const listVideoAccountChannelsValidator = [
 
 const videoChannelsAddValidator = [
   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) => {
     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'),
   body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
   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) => {
     logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
index 6d4fb907bab304226443f375805bb000018cb53a..e91739f814cbbff9d421b4e6bf8452b3e4472704 100644 (file)
@@ -14,7 +14,7 @@ import {
   isVideoLicenceValid,
   isVideoNameValid,
   isVideoPrivacyValid,
-  isVideoRatingTypeValid,
+  isVideoRatingTypeValid, isVideoSupportValid,
   isVideoTagsValid
 } from '../../helpers/custom-validators/videos'
 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
@@ -46,6 +46,7 @@ const videosAddValidator = [
   body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
   body('nsfw').custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
   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('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
   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('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
   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('commentsEnabled').optional().custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
 
index 20724ae0c16cdb5fc671cc17f7a59a0599b47502..d8f305c376814fc306a4314256274684a09dec36 100644 (file)
@@ -1,16 +1,28 @@
 import * as Sequelize from 'sequelize'
 import {
-  AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Model, Table,
+  AllowNull,
+  BeforeDestroy,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  Default,
+  DefaultScope,
+  ForeignKey,
+  HasMany,
+  Is,
+  Model,
+  Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { Account } from '../../../shared/models/actors'
+import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
 import { logger } from '../../helpers/logger'
 import { sendDeleteActor } from '../../lib/activitypub/send'
 import { ActorModel } from '../activitypub/actor'
 import { ApplicationModel } from '../application/application'
 import { AvatarModel } from '../avatar/avatar'
 import { ServerModel } from '../server/server'
-import { getSort } from '../utils'
+import { getSort, throwIfNotValid } from '../utils'
 import { VideoChannelModel } from '../video/video-channel'
 import { VideoCommentModel } from '../video/video-comment'
 import { UserModel } from './user'
@@ -42,6 +54,12 @@ export class AccountModel extends Model<AccountModel> {
   @Column
   name: string
 
+  @AllowNull(true)
+  @Default(null)
+  @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description'))
+  @Column
+  description: string
+
   @CreatedAt
   createdAt: Date
 
@@ -196,6 +214,7 @@ export class AccountModel extends Model<AccountModel> {
     const account = {
       id: this.id,
       displayName: this.name,
+      description: this.description,
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
     }
@@ -204,7 +223,11 @@ export class AccountModel extends Model<AccountModel> {
   }
 
   toActivityPubObject () {
-    return this.Actor.toActivityPubObject(this.name, 'Account')
+    const obj = this.Actor.toActivityPubObject(this.name, 'Account')
+
+    return Object.assign(obj, {
+      summary: this.description
+    })
   }
 
   isOwned () {
index c79bba96bb08b9be8a0add9d3489db7c210407b7..1d0e54ee302f8caf59ba996257005f8f4fc4e9fc 100644 (file)
@@ -2,14 +2,31 @@ import { values } from 'lodash'
 import { extname } from 'path'
 import * as Sequelize from 'sequelize'
 import {
-  AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes,
-  Table, UpdatedAt
+  AllowNull,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  DataType,
+  Default,
+  DefaultScope,
+  ForeignKey,
+  HasMany,
+  HasOne,
+  Is,
+  IsUUID,
+  Model,
+  Scopes,
+  Table,
+  UpdatedAt
 } from 'sequelize-typescript'
 import { ActivityPubActorType } from '../../../shared/models/activitypub'
 import { Avatar } from '../../../shared/models/avatars/avatar.model'
 import { activityPubContextify } from '../../helpers/activitypub'
 import {
-  isActorFollowersCountValid, isActorFollowingCountValid, isActorPreferredUsernameValid, isActorPrivateKeyValid,
+  isActorFollowersCountValid,
+  isActorFollowingCountValid,
+  isActorPreferredUsernameValid,
+  isActorPrivateKeyValid,
   isActorPublicKeyValid
 } from '../../helpers/custom-validators/activitypub/actor'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
index 7c161c8642b295ed77e5c9c6d14f4c66a28cfdd7..289775a0f93312d42e3149bf1c965983ebad04cb 100644 (file)
@@ -1,9 +1,13 @@
 import {
   AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Is, Model, Scopes, Table,
-  UpdatedAt
+  UpdatedAt, Default
 } from 'sequelize-typescript'
 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 { sendDeleteActor } from '../../lib/activitypub/send'
 import { AccountModel } from '../account/account'
@@ -67,10 +71,17 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
   name: string
 
   @AllowNull(true)
+  @Default(null)
   @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description'))
   @Column
   description: string
 
+  @AllowNull(true)
+  @Default(null)
+  @Is('VideoChannelSupport', value => throwIfNotValid(value, isVideoChannelSupportValid, 'support'))
+  @Column
+  support: string
+
   @CreatedAt
   createdAt: Date
 
@@ -221,12 +232,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       .findById(id, options)
   }
 
-  toFormattedJSON () {
+  toFormattedJSON (): VideoChannel {
     const actor = this.Actor.toFormattedJSON()
     const account = {
       id: this.id,
       displayName: this.name,
       description: this.description,
+      support: this.support,
       isLocal: this.Actor.isOwned(),
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
@@ -240,6 +252,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
 
     return Object.assign(obj, {
       summary: this.description,
+      support: this.support,
       attributedTo: [
         {
           type: 'Person' as 'Person',
index 48ba68a4aabfbdc6b77918221d8fa9a6768fd6dc..6f770957faab736814a94baba94ed0e92a8d19fe 100644 (file)
@@ -1,4 +1,5 @@
 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 { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
 import { CONSTRAINTS_FIELDS } from '../../initializers'
@@ -115,7 +116,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
       .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 = {
       attributes: [],
       include: [
@@ -152,4 +153,29 @@ export class VideoShareModel extends Model<VideoShareModel> {
     return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
       .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))
+  }
 }
index ff82fb3b274adc324915e08e7657277ecf94e0b9..d748e81bd51bfb0b648691073e9b8b5bb023e20d 100644 (file)
@@ -40,7 +40,7 @@ import {
   isVideoLanguageValid,
   isVideoLicenceValid,
   isVideoNameValid,
-  isVideoPrivacyValid
+  isVideoPrivacyValid, isVideoSupportValid
 } from '../../helpers/custom-validators/videos'
 import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils'
 import { logger } from '../../helpers/logger'
@@ -299,6 +299,12 @@ export class VideoModel extends Model<VideoModel> {
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max))
   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)
   @Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration'))
   @Column
@@ -841,7 +847,7 @@ export class VideoModel extends Model<VideoModel> {
     return join(STATIC_PATHS.PREVIEWS, this.getPreviewName())
   }
 
-  toFormattedJSON () {
+  toFormattedJSON (): Video {
     let serverHost
 
     if (this.VideoChannel.Account.Actor.Server) {
@@ -875,10 +881,10 @@ export class VideoModel extends Model<VideoModel> {
       embedPath: this.getEmbedPath(),
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
-    } as Video
+    }
   }
 
-  toFormattedDetailsJSON () {
+  toFormattedDetailsJSON (): VideoDetails {
     const formattedJson = this.toFormattedJSON()
 
     // 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 = {
       privacyLabel,
       privacy: this.privacy,
+      support: this.support,
       descriptionPath: this.getDescriptionPath(),
       channel: this.VideoChannel.toFormattedJSON(),
       account: this.VideoChannel.Account.toFormattedJSON(),
@@ -917,7 +924,7 @@ export class VideoModel extends Model<VideoModel> {
         return -1
       })
 
-    return Object.assign(formattedJson, detailsJson) as VideoDetails
+    return Object.assign(formattedJson, detailsJson)
   }
 
   toActivityPubObject (): VideoTorrentObject {
@@ -957,17 +964,6 @@ export class VideoModel extends Model<VideoModel> {
     let dislikesObject
 
     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()
       likesObject = res.likesObject
       dislikesObject = res.dislikesObject
@@ -1032,6 +1028,7 @@ export class VideoModel extends Model<VideoModel> {
       updated: this.updatedAt.toISOString(),
       mediaType: 'text/markdown',
       content: this.getTruncatedDescription(),
+      support: this.support,
       icon: {
         type: 'Image',
         url: this.getThumbnailUrl(baseUrlHttp),
index d9dea0713e8fd42f21fc9458f7dcc8af9df93f5c..ee591d620d7731366d6a09035c120b93e09ce247 100644 (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 })
     })
 
+    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 () {
       const fields = {
         password: 'my super password',
index d073e28f0a6c66a0eb1e8842f3fddd6a375a1b46..43c5462ee8028861724b32311af96d554d5314e2 100644 (file)
@@ -62,7 +62,8 @@ describe('Test videos API validator', function () {
   describe('When adding a video channel', function () {
     const baseCorrectParams = {
       name: 'hello',
-      description: 'super description'
+      description: 'super description',
+      support: 'super support text'
     }
 
     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 })
     })
 
+    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 () {
       await makePostBodyRequest({
         url: server.url,
         path,
         token: server.accessToken,
         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 })
     })
 
+    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 () {
       await makePutBodyRequest({
         url: server.url,
index aa30b721ba5d18b049085a0eaab07b022f59a3e9..1d5c8543d2dba5fb1ead5cbeac52786f6183d5b9 100644 (file)
@@ -102,6 +102,7 @@ describe('Test videos API validator', function () {
         nsfw: false,
         commentsEnabled: true,
         description: 'my super description',
+        support: 'my super support text',
         tags: [ 'tag1', 'tag2' ],
         privacy: VideoPrivacy.PUBLIC,
         channelId
@@ -178,7 +179,14 @@ describe('Test videos API validator', 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
 
       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 () {
-      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 })
     })
index 46ad4879e0b97d08d5df1b0602071adc6d46a6a9..19b8438610ed0a31610d94ba59eceb09c22c6982 100644 (file)
@@ -280,6 +280,7 @@ describe('Test follows', function () {
         language: 3,
         nsfw: true,
         description: 'my super description',
+        support: 'my super support text',
         host: 'localhost:9003',
         account: 'root',
         isLocal,
index 4cedeb89efd61e758e31a4626a4bafcb6b74c52c..84153b0979f204181bf7c27208399d2ab58376fa 100644 (file)
@@ -36,6 +36,7 @@ describe('Test handle downs', function () {
     nsfw: true,
     privacy: VideoPrivacy.PUBLIC,
     description: 'my super description for server 1',
+    support: 'my super support text for server 1',
     tags: [ 'tag1p1', 'tag2p1' ],
     fixture: 'video_short1.webm'
   }
@@ -51,6 +52,7 @@ describe('Test handle downs', function () {
     language: 9,
     nsfw: true,
     description: 'my super description for server 1',
+    support: 'my super support text for server 1',
     host: 'localhost:9001',
     account: 'root',
     isLocal: false,
index 3c4eaff14976ac96a9e97872c8c69abb50d103ba..bb458f7a501322ac7f05a924f7d39eba16a5b9a2 100644 (file)
@@ -3,7 +3,10 @@
 import * as chai from 'chai'
 import 'mocha'
 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 { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../utils/users/accounts'
 import { setAccessTokensToServers } from '../../utils/users/login'
@@ -51,6 +54,22 @@ describe('Test users with multiple servers', function () {
     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 () {
     this.timeout(10000)
 
@@ -70,7 +89,7 @@ describe('Test users with multiple servers', function () {
     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) {
       const resAccounts = await getAccountsList(server.url, '-createdAt')
 
@@ -81,6 +100,7 @@ describe('Test users with multiple servers', function () {
       const rootServer1Get = resAccount.body as Account
       expect(rootServer1Get.name).to.equal('root')
       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')
     }
index ac167d4f97c20e404a3ed2898acf0f8e2c9d9c41..c650a74f54b3f77c4340c0fc04f0d6d5ee4d6733 100644 (file)
@@ -172,6 +172,7 @@ describe('Test users', function () {
     expect(user.videoQuota).to.equal(2 * 1024 * 1024)
     expect(user.roleLabel).to.equal('User')
     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 () {
@@ -315,6 +316,7 @@ describe('Test users', function () {
     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.be.null
   })
 
   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.videoQuota).to.equal(2 * 1024 * 1024)
     expect(user.id).to.be.a('number')
+    expect(user.account.description).to.be.null
   })
 
   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')
   })
 
+  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 () {
     await updateUser({
       url: server.url,
index 85d158d61aefe24c6755bbc3727be663e181a244..c82ac1348e9e8ce743b67fe3f55fba8b33ffecd3 100644 (file)
@@ -70,6 +70,7 @@ describe('Test multiple servers', function () {
         language: 9,
         nsfw: true,
         description: 'my super description for server 1',
+        support: 'my super support text for server 1',
         tags: [ 'tag1p1', 'tag2p1' ],
         channelId: videoChannelId,
         fixture: 'video_short1.webm'
@@ -88,6 +89,7 @@ describe('Test multiple servers', function () {
           language: 9,
           nsfw: true,
           description: 'my super description for server 1',
+          support: 'my super support text for server 1',
           host: 'localhost:9001',
           account: 'root',
           isLocal,
@@ -136,6 +138,7 @@ describe('Test multiple servers', function () {
         language: 11,
         nsfw: true,
         description: 'my super description for server 2',
+        support: 'my super support text for server 2',
         tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
         fixture: 'video_short2.webm',
         thumbnailfile: 'thumbnail.jpg',
@@ -156,6 +159,7 @@ describe('Test multiple servers', function () {
           language: 11,
           nsfw: true,
           description: 'my super description for server 2',
+          support: 'my super support text for server 2',
           host: 'localhost:9002',
           account: 'user1',
           isLocal,
@@ -211,6 +215,7 @@ describe('Test multiple servers', function () {
         language: 11,
         nsfw: true,
         description: 'my super description for server 3',
+        support: 'my super support text for server 3',
         tags: [ 'tag1p3' ],
         fixture: 'video_short3.webm'
       }
@@ -223,6 +228,7 @@ describe('Test multiple servers', function () {
         language: 12,
         nsfw: false,
         description: 'my super description for server 3-2',
+        support: 'my super support text for server 3-2',
         tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
         fixture: 'video_short.webm'
       }
@@ -257,6 +263,7 @@ describe('Test multiple servers', function () {
           language: 11,
           nsfw: true,
           description: 'my super description for server 3',
+          support: 'my super support text for server 3',
           host: 'localhost:9003',
           account: 'root',
           isLocal,
@@ -286,6 +293,7 @@ describe('Test multiple servers', function () {
           language: 12,
           nsfw: false,
           description: 'my super description for server 3-2',
+          support: 'my super support text for server 3-2',
           host: 'localhost:9003',
           account: 'root',
           commentsEnabled: true,
@@ -525,6 +533,7 @@ describe('Test multiple servers', function () {
         language: 13,
         nsfw: true,
         description: 'my super description updated',
+        support: 'my super support text updated',
         tags: [ 'tag_up_1', 'tag_up_2' ],
         thumbnailfile: 'thumbnail.jpg',
         previewfile: 'preview.jpg'
@@ -553,6 +562,7 @@ describe('Test multiple servers', function () {
           language: 13,
           nsfw: true,
           description: 'my super description updated',
+          support: 'my super support text updated',
           host: 'localhost:9003',
           account: 'root',
           isLocal,
@@ -841,6 +851,7 @@ describe('Test multiple servers', function () {
           language: null,
           nsfw: false,
           description: null,
+          support: null,
           host: 'localhost:9002',
           account: 'root',
           isLocal,
index 8f55075fb67d519904179f71008e6537f50ead74..83b6a0e9a5961b33431f796a66ac8eb487b8e17c 100644 (file)
@@ -26,6 +26,7 @@ describe('Test a single server', function () {
     language: 3,
     nsfw: true,
     description: 'my super description',
+    support: 'my super support text',
     host: 'localhost:9001',
     account: 'root',
     isLocal: true,
@@ -54,6 +55,7 @@ describe('Test a single server', function () {
     language: 5,
     nsfw: false,
     description: 'my super description updated',
+    support: 'my super support text updated',
     host: 'localhost:9001',
     account: 'root',
     isLocal: true,
index 25b7ad6abd7cf035afea067711c7b588b1947ba9..b9c9bbf3cbd25efa99e29d9d50fa62005564fd7d 100644 (file)
@@ -1,27 +1,27 @@
 /* tslint:disable:no-unused-expression */
 
-import 'mocha'
 import * as chai from 'chai'
-const expect = chai.expect
-
+import 'mocha'
+import { User } from '../../../../shared/index'
+import { doubleFollow, flushAndRunMultipleServers, uploadVideo, wait } from '../../utils'
 import {
-  ServerInfo,
+  addVideoChannel,
+  deleteVideoChannel,
   flushTests,
-  runServer,
-  setAccessTokensToServers,
-  killallServers,
+  getAccountVideoChannelsList,
   getMyUserInformation,
+  getVideoChannel,
   getVideoChannelsList,
-  addVideoChannel,
-  getAccountVideoChannelsList,
-  updateVideoChannel,
-  deleteVideoChannel,
-  getVideoChannel
+  killallServers,
+  ServerInfo,
+  setAccessTokensToServers,
+  updateVideoChannel
 } from '../../utils/index'
-import { User } from '../../../../shared/index'
 
-describe('Test a video channels', function () {
-  let server: ServerInfo
+const expect = chai.expect
+
+describe('Test video channels', function () {
+  let servers: ServerInfo[]
   let userInfo: User
   let videoChannelId: number
 
@@ -30,29 +30,41 @@ describe('Test a video channels', function () {
 
     await flushTests()
 
-    server = await runServer(1)
+    servers = await flushAndRunMultipleServers(2)
+
+    await setAccessTokensToServers(servers)
+    await doubleFollow(servers[0], servers[1])
 
-    await setAccessTokensToServers([ server ])
+    await wait(5000)
   })
 
   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.data).to.be.an('array')
     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 = {
       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 () => {
-    const res = await getMyUserInformation(server.url, server.accessToken)
+    const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
     userInfo = res.body
 
     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[1].displayName).to.equal('second video channel')
     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 () => {
-    const res = await getAccountVideoChannelsList(server.url, userInfo.account.uuid)
-
+  it('Should have two video channels when getting account channels on server 1', async function () {
+    const res = await getAccountVideoChannelsList(servers[0].url, userInfo.account.uuid)
     expect(res.body.total).to.equal(2)
     expect(res.body.data).to.be.an('array')
     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[1].displayName).to.equal('second video channel')
     expect(videoChannels[1].description).to.equal('super video channel description')
+    expect(videoChannels[1].support).to.equal('super video channel support text')
+  })
+
+  it('Should have one video channel when getting account channels on server 2', async function () {
+    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)
 
-    videoChannelId = videoChannels[1].id
+    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 () => {
-    const res = await getVideoChannelsList(server.url, 1, 1, '-name')
+  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.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')
   })
 
-  it('Should update video channel', async () => {
+  it('Should update video channel', async function () {
+    this.timeout(5000)
+
     const videoChannelAttributes = {
       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 () => {
-    const res = await getVideoChannelsList(server.url, 0, 1, '-name')
+  it('Should have video channel updated', async function () {
+    for (const server of servers) {
+      const res = await getVideoChannelsList(server.url, 0, 1, '-name')
 
-    expect(res.body.total).to.equal(2)
-    expect(res.body.data).to.be.an('array')
-    expect(res.body.data).to.have.lengthOf(1)
-    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.total).to.equal(2)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data).to.have.lengthOf(1)
+      expect(res.body.data[0].displayName).to.equal('video channel updated')
+      expect(res.body.data[0].description).to.equal('video channel description updated')
+      expect(res.body.data[0].support).to.equal('video channel support text updated')
+    }
   })
 
-  it('Should get video channel', async () => {
-    const res = await getVideoChannel(server.url, videoChannelId)
+  it('Should get video channel', async function () {
+    const res = await getVideoChannel(servers[0].url, videoChannelId)
 
     const videoChannel = res.body
     expect(videoChannel.displayName).to.equal('video channel 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 () => {
-    await deleteVideoChannel(server.url, server.accessToken, videoChannelId)
+  it('Should delete video channel', async function () {
+    await deleteVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId)
   })
 
-  it('Should have video channel deleted', async () => {
-    const res = await getVideoChannelsList(server.url, 0, 10)
+  it('Should have video channel deleted', async function () {
+    const res = await getVideoChannelsList(servers[0].url, 0, 10)
 
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.be.an('array')
@@ -129,7 +161,7 @@ describe('Test a video channels', function () {
   })
 
   after(async function () {
-    killallServers([ server ])
+    killallServers(servers)
 
     // Keep the logs if the test failed
     if (this['ok']) {
index 3c9d4624642b2154186b90183aa91d74a41285c9..daf731a14b9e01604b49c8d88a9c520d89ff67c2 100644 (file)
@@ -131,6 +131,7 @@ function updateMyUser (options: {
   displayNSFW?: boolean,
   email?: string,
   autoPlayVideo?: boolean
+  description?: string
 }) {
   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.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
   if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
+  if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
 
   return makePutBodyRequest({
     url: options.url,
index 062ca8f4d099786e723bbcf8fba940097dab58e5..2d095d8ab25fbabe8d26ac8d527ced1c6e4806d4 100644 (file)
@@ -3,6 +3,7 @@ import * as request from 'supertest'
 type VideoChannelAttributes = {
   name?: string
   description?: string
+  support?: 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/)
 }
 
-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'
 
   // Default attributes
   let attributes = {
     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)
 
@@ -54,6 +56,7 @@ function updateVideoChannel (url: string, token: string, channelId: number, attr
 
   if (attributes.name) body['name'] = attributes.name
   if (attributes.description) body['description'] = attributes.description
+  if (attributes.support) body['support'] = attributes.support
 
   return request(url)
     .put(path)
index 9a4af0b9f914c8b8040ae36ce78a116f3b50cf6c..a06078d40bea3a6e5b21e5d182bc90c9a36f40e2 100644 (file)
@@ -248,6 +248,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     channelId: defaultChannelId,
     nsfw: true,
     description: 'my super description',
+    support: 'my super support text',
     tags: [ 'tag' ],
     privacy: VideoPrivacy.PUBLIC,
     commentsEnabled: true,
@@ -277,6 +278,10 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     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) {
     req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
   }
@@ -284,10 +289,6 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     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))
             .expect(specialStatus)
 }
@@ -366,6 +367,7 @@ async function completeVideoCheck (
     nsfw: boolean
     commentsEnabled: boolean
     description: string
+    support: string
     host: string
     account: string
     isLocal: boolean,
index 3bf9dd65e00e0c8ab455746daa3664105b0e57fb..de8ff8d26fef23c61bcff649818b58d24a6f46fc 100644 (file)
@@ -19,6 +19,7 @@ program
   .option('-L, --language <language number>', 'Language number')
   .option('-d, --video-description <description>', 'Video description')
   .option('-t, --tags <tags>', 'Video tags', list)
+  .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
   .option('-f, --file <file>', 'Video absolute file path')
   .parse(process.argv)
 
@@ -72,7 +73,8 @@ async function run () {
     description: program['videoDescription'],
     tags: program['tags'],
     commentsEnabled: program['commentsEnabled'],
-    fixture: program['file']
+    fixture: program['file'],
+    thumbnailfile: program['thumbnailPath']
   }
 
   await uploadVideo(program['url'], accessToken, videoAttributes)
index 78256e9be57fd1b6f63e29c21367a6205f88061f..119bc22d4e621e5a4bcc03ca1b97e686b210918b 100644 (file)
@@ -19,6 +19,7 @@ export interface ActivityPubActor {
   summary: string
   attributedTo: ActivityPubAttributedTo[]
 
+  support?: string
   uuid: string
   publicKey: {
     id: string
@@ -26,11 +27,9 @@ export interface ActivityPubActor {
     publicKeyPem: string
   }
 
-  // Not used
   icon: {
     type: 'Image'
     mediaType: 'image/png'
     url: string
   }
-  // liked: string
 }
index 6f03bf7d0f8caa9745d8ac9b0760caf8f9418730..02820a4cbe9ebd68087c5095988f583aadfc4165 100644 (file)
@@ -23,6 +23,7 @@ export interface VideoTorrentObject {
   updated: string
   mediaType: 'text/markdown'
   content: string
+  support: string
   icon: ActivityIconObject
   url: ActivityUrlObject[]
   likes?: ActivityPubOrderedCollection<string>
index 5cc12c18fc789ddab91ae29e738b7d50b115de48..e1117486dd9e55f2d5b5f99bd73ed2c58ff7dbde 100644 (file)
@@ -2,4 +2,5 @@ import { Actor } from './actor.model'
 
 export interface Account extends Actor {
   displayName: string
+  description: string
 }
index 83417a7bd7440b3e45bc62dc18b7623012e900fb..b8423332910edc1f9f148163b9246b74c9f79b58 100644 (file)
@@ -1,4 +1,5 @@
 export interface UserUpdateMe {
+  description?: string
   displayNSFW?: boolean
   autoPlayVideo?: boolean
   email?: string
index f309c8f45c35feff4da700936455c136cd51bfa9..cd6bae96573a98e91d7ae6d7b63caba5454ccc77 100644 (file)
@@ -1,4 +1,5 @@
 export interface VideoChannelCreate {
   name: string
   description?: string
+  support?: string
 }
index 4e98e39a8a5cdf9caf9e247eb270b5adb51447fd..73a0a670941faf23b49d5096eb499107a0ab79d9 100644 (file)
@@ -1,4 +1,5 @@
 export interface VideoChannelUpdate {
   name: string
-  description: string
+  description?: string
+  support?: string
 }
index b164fb555593a7824e9ee46cc6fb72395a99f960..470295a817e5912bf525fd0d6acefa693bcc24bf 100644 (file)
@@ -4,6 +4,7 @@ import { Video } from './video.model'
 export interface VideoChannel extends Actor {
   displayName: string
   description: string
+  support: string
   isLocal: boolean
   owner?: {
     name: string
index 139c2579e2ae2baf49ce3db5d5c9b26e8502c839..567a4c79a0d86591aa57c62f3bb7003f3561262c 100644 (file)
@@ -5,6 +5,7 @@ export interface VideoCreate {
   licence?: number
   language?: number
   description?: string
+  support?: string
   channelId: number
   nsfw: boolean
   name: string
index fc772f77baa35ae6941239c22d9825042e89a812..0b26484d7aa8da870f77a703a3d2ab8ac6923bc5 100644 (file)
@@ -6,6 +6,7 @@ export interface VideoUpdate {
   licence?: number
   language?: number
   description?: string
+  support?: string
   privacy?: VideoPrivacy
   tags?: string[]
   commentsEnabled?: boolean
index 39d1edc061253f6182ed11a161fb2551fb5e7276..deb81da449e2bf5cf4ace980f8359cfa5364bcd6 100644 (file)
@@ -41,6 +41,7 @@ export interface VideoDetails extends Video {
   privacy: VideoPrivacy
   privacyLabel: string
   descriptionPath: string
+  support: string
   channel: VideoChannel
   tags: string[]
   files: VideoFile[]