import { Actor as ActorServer } from '../../../../../shared/models/actors/actor.model'
-import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
+import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
export abstract class Actor implements ActorServer {
id: number
this.host = hash.host
this.followingCount = hash.followingCount
this.followersCount = hash.followersCount
- this.createdAt = new Date(hash.createdAt.toString())
- this.updatedAt = new Date(hash.updatedAt.toString())
+ this.createdAt = new Date(hash.createdAt)
+ this.updatedAt = new Date(hash.updatedAt)
this.avatar = hash.avatar
this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this)
tags: string[]
nsfw: boolean
commentsEnabled: boolean
- channel: number
+ channelId: number
privacy: VideoPrivacy
support: string
thumbnailfile?: any
this.tags = videoDetails.tags
this.nsfw = videoDetails.nsfw
this.commentsEnabled = videoDetails.commentsEnabled
- this.channel = videoDetails.channel.id
+ this.channelId = videoDetails.channel.id
this.privacy = videoDetails.privacy.id
this.support = videoDetails.support
this.thumbnailUrl = videoDetails.thumbnailUrl
tags: this.tags,
nsfw: this.nsfw,
commentsEnabled: this.commentsEnabled,
- channelId: this.channel,
+ channelId: this.channelId,
privacy: this.privacy
}
}
avatar: Avatar
}
+ channel: {
+ id: number
+ uuid: string
+ name: string
+ displayName: string
+ url: string
+ host: string
+ avatar: Avatar
+ }
+
private static createDurationString (duration: number) {
const hours = Math.floor(duration / 3600)
const minutes = Math.floor(duration % 3600 / 60)
language,
support,
description,
+ channelId: video.channelId,
privacy: video.privacy,
tags: video.tags,
nsfw: video.nsfw,
<div class="col-md-4">
<div class="form-group">
<label>Channel</label>
- <div class="peertube-select-disabled-container">
+ <div class="peertube-select-container">
<select formControlName="channelId">
<option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
</select>
@include peertube-select-container(auto);
}
-.peertube-select-disabled-container {
- @include peertube-select-disabled-container(auto);
-}
-
.form-group-checkbox {
my-help { margin-left: 5px }
}
this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
- this.form.addControl('channelId', new FormControl({ value: '', disabled: true }))
+ this.form.addControl('channelId', new FormControl('', VIDEO_CHANNEL.VALIDATORS))
this.form.addControl('nsfw', new FormControl(false))
this.form.addControl('commentsEnabled', new FormControl(true))
this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS))
const video = new VideoEdit()
video.patch(this.form.value)
- video.channel = this.firstStepChannelId
+ video.channelId = this.firstStepChannelId
video.id = this.videoUploadedIds.id
video.uuid = this.videoUploadedIds.uuid
import { AuthService } from '../../core/auth'
import { FormReactive } from '../../shared'
import { ValidatorMessage } from '../../shared/forms/form-validators/validator-message'
-import { populateAsyncUserVideoChannels } from '../../shared/misc/utils'
import { VideoEdit } from '../../shared/video/video-edit.model'
import { VideoService } from '../../shared/video/video.service'
+import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
@Component({
selector: 'my-videos-update',
video => {
this.video = new VideoEdit(video)
- this.userVideoChannels = [
- {
- id: video.channel.id,
- label: video.channel.displayName
- }
- ]
+ populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
+ .catch(err => console.error(err))
// We cannot set private a video that was not private
if (video.privacy.id !== VideoPrivacy.PRIVATE) {
VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES
} from '../../../initializers'
-import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
+import {
+ changeVideoChannelShare,
+ fetchRemoteVideoDescription,
+ getVideoActivityPubUrl,
+ shareVideoByServerAndChannel
+} from '../../../lib/activitypub'
import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send'
import { JobQueue } from '../../../lib/job-queue'
import { Redis } from '../../../lib/redis'
const sequelizeOptions = {
transaction: t
}
+ const oldVideoChannel = videoInstance.VideoChannel
if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
+ // Video tags update?
if (videoInfoToUpdate.tags) {
const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
- await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
- videoInstance.Tags = tagInstances
+ await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions)
+ videoInstanceUpdated.Tags = tagInstances
}
- // Now we'll update the video's meta data to our friends
- if (wasPrivateVideo === false) {
- await sendUpdateVideo(videoInstanceUpdated, t)
+ // Video channel update?
+ if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) {
+ await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel)
+ videoInstance.VideoChannel = res.locals.videoChannel
+
+ if (wasPrivateVideo === false) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t)
}
+ // Now we'll update the video's meta data to our friends
+ if (wasPrivateVideo === false) await sendUpdateVideo(videoInstanceUpdated, t)
+
// Video is not private anymore, send a create action to remote servers
if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
await sendCreateVideo(videoInstanceUpdated, t)
} from '../../initializers'
import { VideoModel } from '../../models/video/video'
import { exists, isArray, isFileValid } from './misc'
+import { VideoChannelModel } from '../../models/video/video-channel'
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
return true
}
+async function isVideoChannelOfAccountExist (channelId: number, accountId: number, res: Response) {
+ const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, accountId)
+ if (!videoChannel) {
+ res.status(400)
+ .json({ error: 'Unknown video video channel for this account.' })
+ .end()
+
+ return false
+ }
+
+ res.locals.videoChannel = videoChannel
+ return true
+}
+
// ---------------------------------------------------------------------------
export {
isVideoFileSizeValid,
isVideoExist,
isVideoImage,
+ isVideoChannelOfAccountExist,
isVideoSupportValid
}
return videoChannelCreated
}
-async function refreshActorIfNeeded (actor: ActorModel) {
+async function refreshActorIfNeeded (actor: ActorModel): Promise<ActorModel> {
if (!actor.isOutdated()) return actor
try {
-import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub'
+import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub'
import { DislikeObject } from '../../../../shared/models/activitypub/objects'
import { getActorUrl } from '../../../helpers/activitypub'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { forwardActivity } from '../send/misc'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
+import { VideoShareModel } from '../../../models/video/video-share'
async function processUndoActivity (activity: ActivityUndo) {
const activityToUndo = activity.object
return processUndoDislike(actorUrl, activity)
} else if (activityToUndo.type === 'Follow') {
return processUndoFollow(actorUrl, activityToUndo)
+ } else if (activityToUndo.type === 'Announce') {
+ return processUndoAnnounce(actorUrl, activityToUndo)
}
logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
return undefined
})
}
+
+function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
+ const options = {
+ arguments: [ actorUrl, announceActivity ],
+ errorMessage: 'Cannot undo announce with many retries.'
+ }
+
+ return retryTransactionWrapper(undoAnnounce, options)
+}
+
+function undoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
+ return sequelizeTypescript.transaction(async t => {
+ const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
+ if (!share) throw new Error(`'Unknown video share ${announceActivity.id}.`)
+
+ await share.destroy({ transaction: t })
+
+ return undefined
+ })
+}
import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
import {
generateThumbnailFromUrl,
- getOrCreateAccountAndVideoAndChannel,
+ getOrCreateAccountAndVideoAndChannel, getOrCreateVideoChannel,
videoActivityObjectToDBAttributes,
videoFileActivityUrlToDBAttributes
} from '../videos'
const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
+ // Fetch video channel outside the transaction
+ const newVideoChannelActor = await getOrCreateVideoChannel(videoAttributesToUpdate)
+ const newVideoChannel = newVideoChannelActor.VideoChannel
+
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
let videoInstance = res.video
let videoFieldsSave: any
videoFieldsSave = videoInstance.toJSON()
+ // Check actor has the right to update the video
const videoChannel = videoInstance.VideoChannel
if (videoChannel.Account.Actor.id !== actor.id) {
throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
}
- const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to)
+ const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoAttributesToUpdate, activity.to)
videoInstance.set('name', videoData.name)
videoInstance.set('uuid', videoData.uuid)
videoInstance.set('url', videoData.url)
videoInstance.set('updatedAt', videoData.updatedAt)
videoInstance.set('views', videoData.views)
videoInstance.set('privacy', videoData.privacy)
+ videoInstance.set('channelId', videoData.channelId)
await videoInstance.save(sequelizeOptions)
import { Transaction } from 'sequelize'
-import { ActivityAudience, ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub'
+import {
+ ActivityAnnounce,
+ ActivityAudience,
+ ActivityCreate,
+ ActivityFollow,
+ ActivityLike,
+ ActivityUndo
+} from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { VideoModel } from '../../../models/video/video'
import { createActivityData, createDislikeActivityData } from './send-create'
import { followActivityData } from './send-follow'
import { likeActivityData } from './send-like'
+import { VideoShareModel } from '../../../models/video/video-share'
+import { buildVideoAnnounce } from './send-announce'
async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
const me = actorFollow.ActorFollower
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
const dislikeActivity = createDislikeActivityData(byActor, video)
- const object = await createActivityData(undoUrl, byActor, dislikeActivity, t)
+ const object = await createActivityData(dislikeUrl, byActor, dislikeActivity, t)
if (video.isOwned() === false) {
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
}
+async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
+ const undoUrl = getUndoActivityPubUrl(videoShare.url)
+
+ const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
+ const object = await buildVideoAnnounce(byActor, videoShare, video, t)
+ const data = await undoActivityData(undoUrl, byActor, object, t)
+
+ const followersException = [ byActor ]
+ return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
+}
+
// ---------------------------------------------------------------------------
export {
sendUndoFollow,
sendUndoLike,
- sendUndoDislike
+ sendUndoDislike,
+ sendUndoAnnounce
}
// ---------------------------------------------------------------------------
async function undoActivityData (
url: string,
byActor: ActorModel,
- object: ActivityFollow | ActivityLike | ActivityCreate,
+ object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce,
t: Transaction,
audience?: ActivityAudience
): Promise<ActivityUndo> {
import { getServerActor } from '../../helpers/utils'
import { VideoModel } from '../../models/video/video'
import { VideoShareModel } from '../../models/video/video-share'
-import { sendVideoAnnounce } from './send'
+import { sendUndoAnnounce, sendVideoAnnounce } from './send'
import { getAnnounceActivityPubUrl } from './url'
+import { VideoChannelModel } from '../../models/video/video-channel'
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
+ return Promise.all([
+ shareByServer(video, t),
+ shareByVideoChannel(video, t)
+ ])
+}
+
+async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+ await undoShareByVideoChannel(video, oldVideoChannel, t)
+
+ await shareByVideoChannel(video, t)
+}
+
+export {
+ changeVideoChannelShare,
+ shareVideoByServerAndChannel
+}
+
+// ---------------------------------------------------------------------------
+
+async function shareByServer (video: VideoModel, t: Transaction) {
const serverActor = await getServerActor()
const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor)
- const serverSharePromise = VideoShareModel.findOrCreate({
+ return VideoShareModel.findOrCreate({
defaults: {
actorId: serverActor.id,
videoId: video.id,
return undefined
})
+}
+async function shareByVideoChannel (video: VideoModel, t: Transaction) {
const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor)
- const videoChannelSharePromise = VideoShareModel.findOrCreate({
+ return VideoShareModel.findOrCreate({
defaults: {
actorId: video.VideoChannel.actorId,
videoId: video.id,
},
transaction: t
}).then(([ videoChannelShare, created ]) => {
- if (created) return sendVideoAnnounce(serverActor, videoChannelShare, video, t)
+ if (created) return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
return undefined
})
-
- return Promise.all([
- serverSharePromise,
- videoChannelSharePromise
- ])
}
-export {
- shareVideoByServerAndChannel
+async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+ // Load old share
+ const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
+ if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id)
+
+ await sendUndoAnnounce(oldVideoChannel.Actor, oldShare, video, t)
+ await oldShare.destroy({ transaction: t })
}
return attributes
}
+function getOrCreateVideoChannel (videoObject: VideoTorrentObject) {
+ const channel = videoObject.attributedTo.find(a => a.type === 'Group')
+ if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
+
+ return getOrCreateActorAndServerAndModel(channel.id)
+}
+
async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel) {
logger.debug('Adding remote video %s.', videoObject.id)
actor = await getOrCreateActorAndServerAndModel(actorObj.id)
}
- const channel = videoObject.attributedTo.find(a => a.type === 'Group')
- if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
-
- const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
+ const channelActor = await getOrCreateVideoChannel(videoObject)
const options = {
arguments: [ videoObject, channelActor ],
videoActivityObjectToDBAttributes,
videoFileActivityUrlToDBAttributes,
getOrCreateVideo,
+ getOrCreateVideoChannel,
addVideoShares}
// ---------------------------------------------------------------------------
import {
isVideoAbuseReasonValid,
isVideoCategoryValid,
+ isVideoChannelOfAccountExist,
isVideoDescriptionValid,
isVideoExist,
isVideoFile,
import { CONSTRAINTS_FIELDS } from '../../initializers'
import { UserModel } from '../../models/account/user'
import { VideoModel } from '../../models/video/video'
-import { VideoChannelModel } from '../../models/video/video-channel'
import { VideoShareModel } from '../../models/video/video-share'
import { authenticate } from '../oauth'
import { areValidationErrors } from './utils'
.optional()
.toInt()
.custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
- body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
+ body('channelId')
+ .toInt()
+ .custom(isIdValid)
+ .withMessage('Should have correct video channel id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
const videoFile: Express.Multer.File = req.files['videofile'][0]
const user = res.locals.oauth.token.User
- const videoChannel = await VideoChannelModel.loadByIdAndAccount(req.body.channelId, user.Account.id)
- if (!videoChannel) {
- res.status(400)
- .json({ error: 'Unknown video video channel for this account.' })
- .end()
-
- return
- }
-
- res.locals.videoChannel = videoChannel
+ if (!await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
const isAble = await user.isAbleToUploadVideo(videoFile)
if (isAble === false) {
.optional()
.toBoolean()
.custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
+ body('channelId')
+ .optional()
+ .toInt()
+ .custom(isIdValid).withMessage('Should have correct video channel id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosUpdate parameters', { parameters: req.body })
const video = res.locals.video
// Check if the user who did the request is able to update the video
- if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
+ const user = res.locals.oauth.token.User
+ if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
return res.status(409)
.end()
}
+ if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
+
return next()
}
]
})
}
+ static loadByUrl (url: string, t: Sequelize.Transaction) {
+ return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
+ where: {
+ url
+ },
+ transaction: t
+ })
+ }
+
static loadActorsByShare (videoId: number, t: Sequelize.Transaction) {
const query = {
where: {
}
const videoChannelInclude = {
- attributes: [ 'name', 'description' ],
+ attributes: [ 'name', 'description', 'id' ],
model: VideoChannelModel.unscoped(),
required: true,
where: {},
include: [
+ {
+ attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
+ model: ActorModel.unscoped(),
+ required: true,
+ include: [
+ {
+ attributes: [ 'host' ],
+ model: ServerModel.unscoped(),
+ required: false
+ },
+ {
+ model: AvatarModel.unscoped(),
+ required: false
+ }
+ ]
+ },
accountInclude
]
}
}
},
{
- preferredUsername: Sequelize.where(Sequelize.col('preferredUsername'), {
+ preferredUsernameChannel: Sequelize.where(Sequelize.col('VideoChannel->Actor.preferredUsername'), {
[ Sequelize.Op.iLike ]: '%' + value + '%'
})
},
{
- host: Sequelize.where(Sequelize.col('host'), {
+ preferredUsernameAccount: Sequelize.where(Sequelize.col('VideoChannel->Account->Actor.preferredUsername'), {
+ [ Sequelize.Op.iLike ]: '%' + value + '%'
+ })
+ },
+ {
+ host: Sequelize.where(Sequelize.col('VideoChannel->Account->Actor->Server.host'), {
[ Sequelize.Op.iLike ]: '%' + value + '%'
})
}
toFormattedJSON (): Video {
const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
+ const formattedVideoChannel = this.VideoChannel.toFormattedJSON()
return {
id: this.id,
url: formattedAccount.url,
host: formattedAccount.host,
avatar: formattedAccount.avatar
+ },
+ channel: {
+ id: formattedVideoChannel.id,
+ uuid: formattedVideoChannel.uuid,
+ name: formattedVideoChannel.name,
+ displayName: formattedVideoChannel.displayName,
+ url: formattedVideoChannel.url,
+ host: formattedVideoChannel.host,
+ avatar: formattedVideoChannel.avatar
}
}
}
import * as chai from 'chai'
import 'mocha'
-import { User } from '../../../../shared/index'
-import { doubleFollow, flushAndRunMultipleServers, getVideoChannelVideos, uploadVideo, wait } from '../../utils'
+import { User, Video } from '../../../../shared/index'
+import { doubleFollow, flushAndRunMultipleServers, getVideoChannelVideos, updateVideo, uploadVideo, wait } from '../../utils'
import {
addVideoChannel,
deleteVideoChannel,
let servers: ServerInfo[]
let userInfo: User
let accountUUID: string
- let videoChannelId: number
- let videoChannelUUID: string
+ let firstVideoChannelId: number
+ let firstVideoChannelUUID: string
+ let secondVideoChannelId: number
+ let secondVideoChannelUUID: string
+ let videoUUID: string
before(async function () {
this.timeout(30000)
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
const user: User = res.body
accountUUID = user.account.uuid
+
+ firstVideoChannelId = user.videoChannels[0].id
+ firstVideoChannelUUID = user.videoChannels[0].uuid
}
await wait(5000)
it('Should create another video channel', async function () {
this.timeout(10000)
- const videoChannel = {
- displayName: 'second video channel',
- description: 'super video channel description',
- support: 'super video channel support text'
+ {
+ const videoChannel = {
+ displayName: 'second video channel',
+ description: 'super video channel description',
+ support: 'super video channel support text'
+ }
+ const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel)
+ secondVideoChannelId = res.body.videoChannel.id
+ secondVideoChannelUUID = res.body.videoChannel.uuid
}
- const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
- videoChannelId = res.body.videoChannel.id
- videoChannelUUID = res.body.videoChannel.uuid
// The channel is 1 is propagated to servers 2
- await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my video name', channelId: videoChannelId })
+ {
+ const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name', channelId: secondVideoChannelId })
+ videoUUID = res.body.video.uuid
+ }
await wait(3000)
})
support: 'video channel support text updated'
}
- await updateVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId, videoChannelAttributes)
+ await updateVideoChannel(servers[0].url, servers[0].accessToken, secondVideoChannelId, videoChannelAttributes)
await wait(3000)
})
})
it('Should get video channel', async function () {
- const res = await getVideoChannel(servers[0].url, videoChannelId)
+ const res = await getVideoChannel(servers[0].url, secondVideoChannelId)
const videoChannel = res.body
expect(videoChannel.displayName).to.equal('video channel updated')
expect(videoChannel.support).to.equal('video channel support text updated')
})
- it('Should list the video channel videos', async function () {
+ it('Should list the second video channel videos', async function () {
this.timeout(10000)
for (const server of servers) {
- const res = await getVideoChannelVideos(server.url, server.accessToken, videoChannelUUID, 0, 5)
- expect(res.body.total).to.equal(1)
- expect(res.body.data).to.be.an('array')
- expect(res.body.data).to.have.lengthOf(1)
- expect(res.body.data[0].name).to.equal('my video name')
+ const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondVideoChannelUUID, 0, 5)
+ expect(res1.body.total).to.equal(1)
+ expect(res1.body.data).to.be.an('array')
+ expect(res1.body.data).to.have.lengthOf(1)
+ expect(res1.body.data[0].name).to.equal('my video name')
+ }
+ })
+
+ it('Should change the video channel of a video', async function () {
+ this.timeout(10000)
+
+ await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { channelId: firstVideoChannelId })
+
+ await wait(5000)
+ })
+
+ it('Should list the first video channel videos', async function () {
+ this.timeout(10000)
+
+ for (const server of servers) {
+ const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondVideoChannelUUID, 0, 5)
+ expect(res1.body.total).to.equal(0)
+
+ const res2 = await getVideoChannelVideos(server.url, server.accessToken, firstVideoChannelUUID, 0, 5)
+ expect(res2.body.total).to.equal(1)
+
+ const videos: Video[] = res2.body.data
+ expect(videos).to.be.an('array')
+ expect(videos).to.have.lengthOf(1)
+ expect(videos[0].name).to.equal('my video name')
}
})
it('Should delete video channel', async function () {
- await deleteVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId)
+ await deleteVideoChannel(servers[0].url, servers[0].accessToken, secondVideoChannelId)
})
it('Should have video channel deleted', async function () {
ServerInfo,
testImage
} from '../'
-import { VideoPrivacy } from '../../../../shared/models/videos'
+import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
import { readdirPromise } from '../../../helpers/core-utils'
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
import { dateIsValid, webtorrentAdd } from '../index'
if (attributes.description) body['description'] = attributes.description
if (attributes.tags) body['tags'] = attributes.tags
if (attributes.privacy) body['privacy'] = attributes.privacy
+ if (attributes.channelId) body['channelId'] = attributes.channelId
// Upload request
if (attributes.thumbnailfile || attributes.previewfile) {
expect(video.account.uuid).to.be.a('string')
expect(video.account.host).to.equal(attributes.account.host)
expect(video.account.name).to.equal(attributes.account.name)
+ expect(video.channel.displayName).to.equal(attributes.channel.name)
+ expect(video.channel.name).to.have.lengthOf(36)
expect(video.likes).to.equal(attributes.likes)
expect(video.dislikes).to.equal(attributes.dislikes)
expect(video.isLocal).to.equal(attributes.isLocal)
expect(dateIsValid(video.updatedAt)).to.be.true
const res = await getVideo(url, video.uuid)
- const videoDetails = res.body
+ const videoDetails: VideoDetails = res.body
expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
expect(videoDetails.tags).to.deep.equal(attributes.tags)
expect(videoDetails.account.name).to.equal(attributes.account.name)
expect(videoDetails.account.host).to.equal(attributes.account.host)
- expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
-
expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
expect(videoDetails.channel.name).to.have.lengthOf(36)
+ expect(videoDetails.channel.host).to.equal(attributes.account.host)
expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
- expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
- expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true
+ expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
+ expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
+ expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
for (const attributeFile of attributes.files) {
const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
export interface ActivityUndo extends BaseActivity {
type: 'Undo',
- object: ActivityFollow | ActivityLike | ActivityCreate
+ object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce
}
export interface ActivityLike extends BaseActivity {
host: string
followingCount: number
followersCount: number
- createdAt: Date
- updatedAt: Date
+ createdAt: Date | string
+ updatedAt: Date | string
avatar: Avatar
}
tags?: string[]
commentsEnabled?: boolean
nsfw?: boolean
+ channelId?: number
thumbnailfile?: Blob
previewfile?: Blob
}
host: string
avatar: Avatar
}
+
+ channel: {
+ id: number
+ uuid: string
+ name: string
+ displayName: string
+ url: string
+ host: string
+ avatar: Avatar
+ }
}
export interface VideoDetails extends Video {