From 2422c46b27790d94fd29a7092170cee5a1b56008 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 15 Feb 2018 14:46:26 +0100 Subject: [PATCH] Implement support field in video and video channel --- .../src/app/shared/account/account.model.ts | 1 + .../app/shared/video/video-details.model.ts | 1 + server/controllers/api/users.ts | 16 ++- server/controllers/api/videos/channel.ts | 23 ++-- server/controllers/api/videos/index.ts | 2 + server/helpers/activitypub.ts | 3 +- server/helpers/custom-validators/accounts.ts | 7 +- server/helpers/custom-validators/users.ts | 5 + .../custom-validators/video-channels.ts | 5 + server/helpers/custom-validators/videos.ts | 7 +- server/initializers/constants.ts | 7 +- .../initializers/migrations/0195-support.ts | 53 ++++++++ server/lib/activitypub/actor.ts | 13 +- .../lib/activitypub/process/process-update.ts | 52 +++++--- server/lib/activitypub/send/send-update.ts | 23 +++- server/lib/activitypub/videos.ts | 6 + server/lib/video-channel.ts | 1 + server/middlewares/validators/users.ts | 2 + .../middlewares/validators/video-channels.ts | 6 +- server/middlewares/validators/videos.ts | 4 +- server/models/account/account.ts | 29 ++++- server/models/activitypub/actor.ts | 23 +++- server/models/video/video-channel.ts | 19 ++- server/models/video/video-share.ts | 28 +++- server/models/video/video.ts | 29 ++--- server/tests/api/check-params/users.ts | 8 ++ .../tests/api/check-params/video-channels.ts | 15 ++- server/tests/api/check-params/videos.ts | 18 ++- server/tests/api/server/follows.ts | 1 + server/tests/api/server/handle-down.ts | 2 + .../tests/api/users/users-multiple-servers.ts | 24 +++- server/tests/api/users/users.ts | 21 +++ server/tests/api/videos/multiple-servers.ts | 11 ++ server/tests/api/videos/single-server.ts | 2 + server/tests/api/videos/video-channels.ts | 122 +++++++++++------- server/tests/utils/users/users.ts | 2 + server/tests/utils/videos/video-channels.ts | 7 +- server/tests/utils/videos/videos.ts | 10 +- server/tools/upload.ts | 4 +- .../models/activitypub/activitypub-actor.ts | 3 +- .../objects/video-torrent-object.ts | 1 + shared/models/actors/account.model.ts | 1 + shared/models/users/user-update-me.model.ts | 1 + .../videos/video-channel-create.model.ts | 1 + .../videos/video-channel-update.model.ts | 3 +- shared/models/videos/video-channel.model.ts | 1 + shared/models/videos/video-create.model.ts | 1 + shared/models/videos/video-update.model.ts | 1 + shared/models/videos/video.model.ts | 1 + 49 files changed, 485 insertions(+), 141 deletions(-) create mode 100644 server/initializers/migrations/0195-support.ts diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts index dffca783b..0bdc76478 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/account/account.model.ts @@ -8,6 +8,7 @@ export class Account implements ServerAccount { url: string name: string displayName: string + description: string host: string followingCount: number followersCount: number diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index cf6b71b60..c746bfd66 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts @@ -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 diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index e3067584e..583376c38 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts @@ -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 }) diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts index 8ec53d9ae..fba5681de 100644 --- a/server/controllers/api/videos/channel.ts +++ b/server/controllers/api/videos/channel.ts @@ -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) diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 564ccd3f8..c9334676e 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -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) diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index eaee324eb..d64a6dd78 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -19,7 +19,8 @@ function activityPubContextify (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: { diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts index 8dc5d1f0d..a46ffc162 100644 --- a/server/helpers/custom-validators/accounts.ts +++ b/server/helpers/custom-validators/accounts.ts @@ -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 @@ -48,5 +52,6 @@ async function isAccountExist (p: Bluebird, res: Response) { export { isAccountIdExist, isLocalAccountNameExist, + isAccountDescriptionValid, isAccountNameValid } diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index e805313f8..bbc7cc199 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -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 } diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index 6bc96bf51..2a6f56840 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts @@ -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 } diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 8ef3a3c64..a46d715ba 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -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 } diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 91fbbde75..ac001bbc7 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -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 index 000000000..8722a5f22 --- /dev/null +++ b/server/initializers/migrations/0195-support.ts @@ -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 { + { + 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 +} diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index c3255d8ca..897acee85 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts @@ -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 { 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 diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index e8f11edd0..622648308 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -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 } diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 40e9318e3..e65362190 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -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, diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 569b8f29d..9f7ed9297 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts @@ -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 } diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index cba15c8d3..49bc0bb56 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts @@ -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'), diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts index 86bddde82..fe42105e8 100644 --- a/server/middlewares/validators/video-channels.ts +++ b/server/middlewares/validators/video-channels.ts @@ -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 }) diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 6d4fb907b..e91739f81 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts @@ -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'), diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 20724ae0c..d8f305c37 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -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 { @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 { 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 { } toActivityPubObject () { - return this.Actor.toActivityPubObject(this.name, 'Account') + const obj = this.Actor.toActivityPubObject(this.name, 'Account') + + return Object.assign(obj, { + summary: this.description + }) } isOwned () { diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index c79bba96b..1d0e54ee3 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -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' diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 7c161c864..289775a0f 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -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 { 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 { .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 { return Object.assign(obj, { summary: this.description, + support: this.support, attributedTo: [ { type: 'Person' as 'Person', diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index 48ba68a4a..6f770957f 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -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 { .then(res => res.map(r => r.Actor)) } - static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction) { + static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction): Bluebird { const query = { attributes: [], include: [ @@ -152,4 +153,29 @@ export class VideoShareModel extends Model { return VideoShareModel.scope(ScopeNames.FULL).findAll(query) .then(res => res.map(r => r.Actor)) } + + static loadActorsByVideoChannel (videoChannelId: number, t: Sequelize.Transaction): Bluebird { + 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)) + } } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index ff82fb3b2..d748e81bd 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -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 { @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 { 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 { 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 { 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 { 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 { 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 { updated: this.updatedAt.toISOString(), mediaType: 'text/markdown', content: this.getTruncatedDescription(), + support: this.support, icon: { type: 'Image', url: this.getThumbnailUrl(baseUrlHttp), diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index d9dea0713..ee591d620 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts @@ -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', diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts index d073e28f0..43c5462ee 100644 --- a/server/tests/api/check-params/video-channels.ts +++ b/server/tests/api/check-params/video-channels.ts @@ -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, diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index aa30b721b..1d5c8543d 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -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 }) }) diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index 46ad4879e..19b843861 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts @@ -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, diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index 4cedeb89e..84153b097 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts @@ -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, diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts index 3c4eaff14..bb458f7a5 100644 --- a/server/tests/api/users/users-multiple-servers.ts +++ b/server/tests/api/users/users-multiple-servers.ts @@ -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') } diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index ac167d4f9..c650a74f5 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts @@ -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, diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 85d158d61..c82ac1348 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts @@ -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, diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 8f55075fb..83b6a0e9a 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts @@ -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, diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 25b7ad6ab..b9c9bbf3c 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts @@ -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']) { diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts index 3c9d46246..daf731a14 100644 --- a/server/tests/utils/users/users.ts +++ b/server/tests/utils/users/users.ts @@ -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, diff --git a/server/tests/utils/videos/video-channels.ts b/server/tests/utils/videos/video-channels.ts index 062ca8f4d..2d095d8ab 100644 --- a/server/tests/utils/videos/video-channels.ts +++ b/server/tests/utils/videos/video-channels.ts @@ -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) diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index 9a4af0b9f..a06078d40 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts @@ -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, diff --git a/server/tools/upload.ts b/server/tools/upload.ts index 3bf9dd65e..de8ff8d26 100644 --- a/server/tools/upload.ts +++ b/server/tools/upload.ts @@ -19,6 +19,7 @@ program .option('-L, --language ', 'Language number') .option('-d, --video-description ', 'Video description') .option('-t, --tags ', 'Video tags', list) + .option('-b, --thumbnail ', 'Thumbnail path') .option('-f, --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) diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts index 78256e9be..119bc22d4 100644 --- a/shared/models/activitypub/activitypub-actor.ts +++ b/shared/models/activitypub/activitypub-actor.ts @@ -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 } diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts index 6f03bf7d0..02820a4cb 100644 --- a/shared/models/activitypub/objects/video-torrent-object.ts +++ b/shared/models/activitypub/objects/video-torrent-object.ts @@ -23,6 +23,7 @@ export interface VideoTorrentObject { updated: string mediaType: 'text/markdown' content: string + support: string icon: ActivityIconObject url: ActivityUrlObject[] likes?: ActivityPubOrderedCollection diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts index 5cc12c18f..e1117486d 100644 --- a/shared/models/actors/account.model.ts +++ b/shared/models/actors/account.model.ts @@ -2,4 +2,5 @@ import { Actor } from './actor.model' export interface Account extends Actor { displayName: string + description: string } diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index 83417a7bd..b84233329 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts @@ -1,4 +1,5 @@ export interface UserUpdateMe { + description?: string displayNSFW?: boolean autoPlayVideo?: boolean email?: string diff --git a/shared/models/videos/video-channel-create.model.ts b/shared/models/videos/video-channel-create.model.ts index f309c8f45..cd6bae965 100644 --- a/shared/models/videos/video-channel-create.model.ts +++ b/shared/models/videos/video-channel-create.model.ts @@ -1,4 +1,5 @@ export interface VideoChannelCreate { name: string description?: string + support?: string } diff --git a/shared/models/videos/video-channel-update.model.ts b/shared/models/videos/video-channel-update.model.ts index 4e98e39a8..73a0a6709 100644 --- a/shared/models/videos/video-channel-update.model.ts +++ b/shared/models/videos/video-channel-update.model.ts @@ -1,4 +1,5 @@ export interface VideoChannelUpdate { name: string - description: string + description?: string + support?: string } diff --git a/shared/models/videos/video-channel.model.ts b/shared/models/videos/video-channel.model.ts index b164fb555..470295a81 100644 --- a/shared/models/videos/video-channel.model.ts +++ b/shared/models/videos/video-channel.model.ts @@ -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 diff --git a/shared/models/videos/video-create.model.ts b/shared/models/videos/video-create.model.ts index 139c2579e..567a4c79a 100644 --- a/shared/models/videos/video-create.model.ts +++ b/shared/models/videos/video-create.model.ts @@ -5,6 +5,7 @@ export interface VideoCreate { licence?: number language?: number description?: string + support?: string channelId: number nsfw: boolean name: string diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts index fc772f77b..0b26484d7 100644 --- a/shared/models/videos/video-update.model.ts +++ b/shared/models/videos/video-update.model.ts @@ -6,6 +6,7 @@ export interface VideoUpdate { licence?: number language?: number description?: string + support?: string privacy?: VideoPrivacy tags?: string[] commentsEnabled?: boolean diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 39d1edc06..deb81da44 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts @@ -41,6 +41,7 @@ export interface VideoDetails extends Video { privacy: VideoPrivacy privacyLabel: string descriptionPath: string + support: string channel: VideoChannel tags: string[] files: VideoFile[] -- 2.41.0