mirror of https://github.com/Chocobozzz/PeerTube
Add ability to update a video channel
parent
9675333dec
commit
0f320037e6
|
@ -1,6 +1,6 @@
|
||||||
import { Actor as ActorServer } from '../../../../../shared/models/actors/actor.model'
|
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 { Avatar } from '../../../../../shared/models/avatars/avatar.model'
|
||||||
|
import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
|
||||||
|
|
||||||
export abstract class Actor implements ActorServer {
|
export abstract class Actor implements ActorServer {
|
||||||
id: number
|
id: number
|
||||||
|
@ -41,8 +41,8 @@ export abstract class Actor implements ActorServer {
|
||||||
this.host = hash.host
|
this.host = hash.host
|
||||||
this.followingCount = hash.followingCount
|
this.followingCount = hash.followingCount
|
||||||
this.followersCount = hash.followersCount
|
this.followersCount = hash.followersCount
|
||||||
this.createdAt = new Date(hash.createdAt.toString())
|
this.createdAt = new Date(hash.createdAt)
|
||||||
this.updatedAt = new Date(hash.updatedAt.toString())
|
this.updatedAt = new Date(hash.updatedAt)
|
||||||
this.avatar = hash.avatar
|
this.avatar = hash.avatar
|
||||||
|
|
||||||
this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this)
|
this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this)
|
||||||
|
|
|
@ -10,7 +10,7 @@ export class VideoEdit {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
commentsEnabled: boolean
|
commentsEnabled: boolean
|
||||||
channel: number
|
channelId: number
|
||||||
privacy: VideoPrivacy
|
privacy: VideoPrivacy
|
||||||
support: string
|
support: string
|
||||||
thumbnailfile?: any
|
thumbnailfile?: any
|
||||||
|
@ -32,7 +32,7 @@ export class VideoEdit {
|
||||||
this.tags = videoDetails.tags
|
this.tags = videoDetails.tags
|
||||||
this.nsfw = videoDetails.nsfw
|
this.nsfw = videoDetails.nsfw
|
||||||
this.commentsEnabled = videoDetails.commentsEnabled
|
this.commentsEnabled = videoDetails.commentsEnabled
|
||||||
this.channel = videoDetails.channel.id
|
this.channelId = videoDetails.channel.id
|
||||||
this.privacy = videoDetails.privacy.id
|
this.privacy = videoDetails.privacy.id
|
||||||
this.support = videoDetails.support
|
this.support = videoDetails.support
|
||||||
this.thumbnailUrl = videoDetails.thumbnailUrl
|
this.thumbnailUrl = videoDetails.thumbnailUrl
|
||||||
|
@ -57,7 +57,7 @@ export class VideoEdit {
|
||||||
tags: this.tags,
|
tags: this.tags,
|
||||||
nsfw: this.nsfw,
|
nsfw: this.nsfw,
|
||||||
commentsEnabled: this.commentsEnabled,
|
commentsEnabled: this.commentsEnabled,
|
||||||
channelId: this.channel,
|
channelId: this.channelId,
|
||||||
privacy: this.privacy
|
privacy: this.privacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,16 @@ export class Video implements VideoServerModel {
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel: {
|
||||||
|
id: number
|
||||||
|
uuid: string
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
url: string
|
||||||
|
host: string
|
||||||
|
avatar: Avatar
|
||||||
|
}
|
||||||
|
|
||||||
private static createDurationString (duration: number) {
|
private static createDurationString (duration: number) {
|
||||||
const hours = Math.floor(duration / 3600)
|
const hours = Math.floor(duration / 3600)
|
||||||
const minutes = Math.floor(duration % 3600 / 60)
|
const minutes = Math.floor(duration % 3600 / 60)
|
||||||
|
|
|
@ -67,6 +67,7 @@ export class VideoService {
|
||||||
language,
|
language,
|
||||||
support,
|
support,
|
||||||
description,
|
description,
|
||||||
|
channelId: video.channelId,
|
||||||
privacy: video.privacy,
|
privacy: video.privacy,
|
||||||
tags: video.tags,
|
tags: video.tags,
|
||||||
nsfw: video.nsfw,
|
nsfw: video.nsfw,
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Channel</label>
|
<label>Channel</label>
|
||||||
<div class="peertube-select-disabled-container">
|
<div class="peertube-select-container">
|
||||||
<select formControlName="channelId">
|
<select formControlName="channelId">
|
||||||
<option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
|
<option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -5,10 +5,6 @@
|
||||||
@include peertube-select-container(auto);
|
@include peertube-select-container(auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
.peertube-select-disabled-container {
|
|
||||||
@include peertube-select-disabled-container(auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-checkbox {
|
.form-group-checkbox {
|
||||||
my-help { margin-left: 5px }
|
my-help { margin-left: 5px }
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class VideoEditComponent implements OnInit {
|
||||||
|
|
||||||
this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
|
this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
|
||||||
this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.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('nsfw', new FormControl(false))
|
||||||
this.form.addControl('commentsEnabled', new FormControl(true))
|
this.form.addControl('commentsEnabled', new FormControl(true))
|
||||||
this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS))
|
this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS))
|
||||||
|
|
|
@ -220,7 +220,7 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy
|
||||||
|
|
||||||
const video = new VideoEdit()
|
const video = new VideoEdit()
|
||||||
video.patch(this.form.value)
|
video.patch(this.form.value)
|
||||||
video.channel = this.firstStepChannelId
|
video.channelId = this.firstStepChannelId
|
||||||
video.id = this.videoUploadedIds.id
|
video.id = this.videoUploadedIds.id
|
||||||
video.uuid = this.videoUploadedIds.uuid
|
video.uuid = this.videoUploadedIds.uuid
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ import { ServerService } from '../../core'
|
||||||
import { AuthService } from '../../core/auth'
|
import { AuthService } from '../../core/auth'
|
||||||
import { FormReactive } from '../../shared'
|
import { FormReactive } from '../../shared'
|
||||||
import { ValidatorMessage } from '../../shared/forms/form-validators/validator-message'
|
import { ValidatorMessage } from '../../shared/forms/form-validators/validator-message'
|
||||||
import { populateAsyncUserVideoChannels } from '../../shared/misc/utils'
|
|
||||||
import { VideoEdit } from '../../shared/video/video-edit.model'
|
import { VideoEdit } from '../../shared/video/video-edit.model'
|
||||||
import { VideoService } from '../../shared/video/video.service'
|
import { VideoService } from '../../shared/video/video.service'
|
||||||
|
import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-update',
|
selector: 'my-videos-update',
|
||||||
|
@ -64,12 +64,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
video => {
|
video => {
|
||||||
this.video = new VideoEdit(video)
|
this.video = new VideoEdit(video)
|
||||||
|
|
||||||
this.userVideoChannels = [
|
populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
|
||||||
{
|
.catch(err => console.error(err))
|
||||||
id: video.channel.id,
|
|
||||||
label: video.channel.displayName
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// We cannot set private a video that was not private
|
// We cannot set private a video that was not private
|
||||||
if (video.privacy.id !== VideoPrivacy.PRIVATE) {
|
if (video.privacy.id !== VideoPrivacy.PRIVATE) {
|
||||||
|
|
|
@ -19,7 +19,12 @@ import {
|
||||||
VIDEO_MIMETYPE_EXT,
|
VIDEO_MIMETYPE_EXT,
|
||||||
VIDEO_PRIVACIES
|
VIDEO_PRIVACIES
|
||||||
} from '../../../initializers'
|
} 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 { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send'
|
||||||
import { JobQueue } from '../../../lib/job-queue'
|
import { JobQueue } from '../../../lib/job-queue'
|
||||||
import { Redis } from '../../../lib/redis'
|
import { Redis } from '../../../lib/redis'
|
||||||
|
@ -305,6 +310,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
const sequelizeOptions = {
|
const sequelizeOptions = {
|
||||||
transaction: t
|
transaction: t
|
||||||
}
|
}
|
||||||
|
const oldVideoChannel = videoInstance.VideoChannel
|
||||||
|
|
||||||
if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
|
if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
|
||||||
if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
|
if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
|
||||||
|
@ -325,17 +331,24 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
|
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
|
||||||
|
|
||||||
|
// Video tags update?
|
||||||
if (videoInfoToUpdate.tags) {
|
if (videoInfoToUpdate.tags) {
|
||||||
const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
|
const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
|
||||||
|
|
||||||
await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
|
await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions)
|
||||||
videoInstance.Tags = tagInstances
|
videoInstanceUpdated.Tags = tagInstances
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Now we'll update the video's meta data to our friends
|
||||||
if (wasPrivateVideo === false) {
|
if (wasPrivateVideo === false) await sendUpdateVideo(videoInstanceUpdated, t)
|
||||||
await sendUpdateVideo(videoInstanceUpdated, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video is not private anymore, send a create action to remote servers
|
// Video is not private anymore, send a create action to remote servers
|
||||||
if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
|
if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from '../../initializers'
|
} from '../../initializers'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { exists, isArray, isFileValid } from './misc'
|
import { exists, isArray, isFileValid } from './misc'
|
||||||
|
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||||
|
|
||||||
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
|
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
|
||||||
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
|
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
|
||||||
|
@ -124,6 +125,20 @@ async function isVideoExist (id: string, res: Response) {
|
||||||
return true
|
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 {
|
export {
|
||||||
|
@ -146,5 +161,6 @@ export {
|
||||||
isVideoFileSizeValid,
|
isVideoFileSizeValid,
|
||||||
isVideoExist,
|
isVideoExist,
|
||||||
isVideoImage,
|
isVideoImage,
|
||||||
|
isVideoChannelOfAccountExist,
|
||||||
isVideoSupportValid
|
isVideoSupportValid
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,7 +353,7 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
|
||||||
return videoChannelCreated
|
return videoChannelCreated
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshActorIfNeeded (actor: ActorModel) {
|
async function refreshActorIfNeeded (actor: ActorModel): Promise<ActorModel> {
|
||||||
if (!actor.isOutdated()) return actor
|
if (!actor.isOutdated()) return actor
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
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 { DislikeObject } from '../../../../shared/models/activitypub/objects'
|
||||||
import { getActorUrl } from '../../../helpers/activitypub'
|
import { getActorUrl } from '../../../helpers/activitypub'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
|
@ -10,6 +10,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||||
import { forwardActivity } from '../send/misc'
|
import { forwardActivity } from '../send/misc'
|
||||||
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
|
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
|
||||||
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
|
|
||||||
async function processUndoActivity (activity: ActivityUndo) {
|
async function processUndoActivity (activity: ActivityUndo) {
|
||||||
const activityToUndo = activity.object
|
const activityToUndo = activity.object
|
||||||
|
@ -22,6 +23,8 @@ async function processUndoActivity (activity: ActivityUndo) {
|
||||||
return processUndoDislike(actorUrl, activity)
|
return processUndoDislike(actorUrl, activity)
|
||||||
} else if (activityToUndo.type === 'Follow') {
|
} else if (activityToUndo.type === 'Follow') {
|
||||||
return processUndoFollow(actorUrl, activityToUndo)
|
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 })
|
logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
|
||||||
|
@ -123,3 +126,23 @@ function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
|
||||||
return undefined
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { VideoFileModel } from '../../../models/video/video-file'
|
||||||
import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
|
import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
|
||||||
import {
|
import {
|
||||||
generateThumbnailFromUrl,
|
generateThumbnailFromUrl,
|
||||||
getOrCreateAccountAndVideoAndChannel,
|
getOrCreateAccountAndVideoAndChannel, getOrCreateVideoChannel,
|
||||||
videoActivityObjectToDBAttributes,
|
videoActivityObjectToDBAttributes,
|
||||||
videoFileActivityUrlToDBAttributes
|
videoFileActivityUrlToDBAttributes
|
||||||
} from '../videos'
|
} from '../videos'
|
||||||
|
@ -54,6 +54,10 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
|
|
||||||
const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
|
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)
|
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
|
||||||
let videoInstance = res.video
|
let videoInstance = res.video
|
||||||
let videoFieldsSave: any
|
let videoFieldsSave: any
|
||||||
|
@ -66,12 +70,13 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
|
|
||||||
videoFieldsSave = videoInstance.toJSON()
|
videoFieldsSave = videoInstance.toJSON()
|
||||||
|
|
||||||
|
// Check actor has the right to update the video
|
||||||
const videoChannel = videoInstance.VideoChannel
|
const videoChannel = videoInstance.VideoChannel
|
||||||
if (videoChannel.Account.Actor.id !== actor.id) {
|
if (videoChannel.Account.Actor.id !== actor.id) {
|
||||||
throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
|
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('name', videoData.name)
|
||||||
videoInstance.set('uuid', videoData.uuid)
|
videoInstance.set('uuid', videoData.uuid)
|
||||||
videoInstance.set('url', videoData.url)
|
videoInstance.set('url', videoData.url)
|
||||||
|
@ -87,6 +92,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
videoInstance.set('updatedAt', videoData.updatedAt)
|
videoInstance.set('updatedAt', videoData.updatedAt)
|
||||||
videoInstance.set('views', videoData.views)
|
videoInstance.set('views', videoData.views)
|
||||||
videoInstance.set('privacy', videoData.privacy)
|
videoInstance.set('privacy', videoData.privacy)
|
||||||
|
videoInstance.set('channelId', videoData.channelId)
|
||||||
|
|
||||||
await videoInstance.save(sequelizeOptions)
|
await videoInstance.save(sequelizeOptions)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { Transaction } from 'sequelize'
|
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 { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
@ -16,6 +23,8 @@ import {
|
||||||
import { createActivityData, createDislikeActivityData } from './send-create'
|
import { createActivityData, createDislikeActivityData } from './send-create'
|
||||||
import { followActivityData } from './send-follow'
|
import { followActivityData } from './send-follow'
|
||||||
import { likeActivityData } from './send-like'
|
import { likeActivityData } from './send-like'
|
||||||
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
|
import { buildVideoAnnounce } from './send-announce'
|
||||||
|
|
||||||
async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
|
async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
|
||||||
const me = actorFollow.ActorFollower
|
const me = actorFollow.ActorFollower
|
||||||
|
@ -58,7 +67,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
|
||||||
|
|
||||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||||
const dislikeActivity = createDislikeActivityData(byActor, video)
|
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) {
|
if (video.isOwned() === false) {
|
||||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||||
|
@ -73,12 +82,24 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
|
||||||
return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
|
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 {
|
export {
|
||||||
sendUndoFollow,
|
sendUndoFollow,
|
||||||
sendUndoLike,
|
sendUndoLike,
|
||||||
sendUndoDislike
|
sendUndoDislike,
|
||||||
|
sendUndoAnnounce
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -86,7 +107,7 @@ export {
|
||||||
async function undoActivityData (
|
async function undoActivityData (
|
||||||
url: string,
|
url: string,
|
||||||
byActor: ActorModel,
|
byActor: ActorModel,
|
||||||
object: ActivityFollow | ActivityLike | ActivityCreate,
|
object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce,
|
||||||
t: Transaction,
|
t: Transaction,
|
||||||
audience?: ActivityAudience
|
audience?: ActivityAudience
|
||||||
): Promise<ActivityUndo> {
|
): Promise<ActivityUndo> {
|
||||||
|
|
|
@ -3,16 +3,37 @@ import { VideoPrivacy } from '../../../shared/models/videos'
|
||||||
import { getServerActor } from '../../helpers/utils'
|
import { getServerActor } from '../../helpers/utils'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { sendVideoAnnounce } from './send'
|
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
|
||||||
import { getAnnounceActivityPubUrl } from './url'
|
import { getAnnounceActivityPubUrl } from './url'
|
||||||
|
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||||
|
|
||||||
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
||||||
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
|
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 serverActor = await getServerActor()
|
||||||
|
|
||||||
const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor)
|
const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor)
|
||||||
const serverSharePromise = VideoShareModel.findOrCreate({
|
return VideoShareModel.findOrCreate({
|
||||||
defaults: {
|
defaults: {
|
||||||
actorId: serverActor.id,
|
actorId: serverActor.id,
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
|
@ -27,9 +48,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareByVideoChannel (video: VideoModel, t: Transaction) {
|
||||||
const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor)
|
const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor)
|
||||||
const videoChannelSharePromise = VideoShareModel.findOrCreate({
|
return VideoShareModel.findOrCreate({
|
||||||
defaults: {
|
defaults: {
|
||||||
actorId: video.VideoChannel.actorId,
|
actorId: video.VideoChannel.actorId,
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
|
@ -40,17 +63,17 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
|
||||||
},
|
},
|
||||||
transaction: t
|
transaction: t
|
||||||
}).then(([ videoChannelShare, created ]) => {
|
}).then(([ videoChannelShare, created ]) => {
|
||||||
if (created) return sendVideoAnnounce(serverActor, videoChannelShare, video, t)
|
if (created) return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
serverSharePromise,
|
|
||||||
videoChannelSharePromise
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
|
||||||
shareVideoByServerAndChannel
|
// 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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,13 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObje
|
||||||
return attributes
|
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) {
|
async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel) {
|
||||||
logger.debug('Adding remote video %s.', videoObject.id)
|
logger.debug('Adding remote video %s.', videoObject.id)
|
||||||
|
|
||||||
|
@ -199,10 +206,7 @@ async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentOb
|
||||||
actor = await getOrCreateActorAndServerAndModel(actorObj.id)
|
actor = await getOrCreateActorAndServerAndModel(actorObj.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = videoObject.attributedTo.find(a => a.type === 'Group')
|
const channelActor = await getOrCreateVideoChannel(videoObject)
|
||||||
if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
|
|
||||||
|
|
||||||
const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
arguments: [ videoObject, channelActor ],
|
arguments: [ videoObject, channelActor ],
|
||||||
|
@ -301,6 +305,7 @@ export {
|
||||||
videoActivityObjectToDBAttributes,
|
videoActivityObjectToDBAttributes,
|
||||||
videoFileActivityUrlToDBAttributes,
|
videoFileActivityUrlToDBAttributes,
|
||||||
getOrCreateVideo,
|
getOrCreateVideo,
|
||||||
|
getOrCreateVideoChannel,
|
||||||
addVideoShares}
|
addVideoShares}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, t
|
||||||
import {
|
import {
|
||||||
isVideoAbuseReasonValid,
|
isVideoAbuseReasonValid,
|
||||||
isVideoCategoryValid,
|
isVideoCategoryValid,
|
||||||
|
isVideoChannelOfAccountExist,
|
||||||
isVideoDescriptionValid,
|
isVideoDescriptionValid,
|
||||||
isVideoExist,
|
isVideoExist,
|
||||||
isVideoFile,
|
isVideoFile,
|
||||||
|
@ -23,7 +24,6 @@ import { logger } from '../../helpers/logger'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
import { UserModel } from '../../models/account/user'
|
import { UserModel } from '../../models/account/user'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { authenticate } from '../oauth'
|
import { authenticate } from '../oauth'
|
||||||
import { areValidationErrors } from './utils'
|
import { areValidationErrors } from './utils'
|
||||||
|
@ -75,7 +75,10 @@ const videosAddValidator = [
|
||||||
.optional()
|
.optional()
|
||||||
.toInt()
|
.toInt()
|
||||||
.custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
|
.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) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
|
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
|
||||||
|
@ -86,16 +89,7 @@ const videosAddValidator = [
|
||||||
const videoFile: Express.Multer.File = req.files['videofile'][0]
|
const videoFile: Express.Multer.File = req.files['videofile'][0]
|
||||||
const user = res.locals.oauth.token.User
|
const user = res.locals.oauth.token.User
|
||||||
|
|
||||||
const videoChannel = await VideoChannelModel.loadByIdAndAccount(req.body.channelId, user.Account.id)
|
if (!await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
|
||||||
if (!videoChannel) {
|
|
||||||
res.status(400)
|
|
||||||
.json({ error: 'Unknown video video channel for this account.' })
|
|
||||||
.end()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.videoChannel = videoChannel
|
|
||||||
|
|
||||||
const isAble = await user.isAbleToUploadVideo(videoFile)
|
const isAble = await user.isAbleToUploadVideo(videoFile)
|
||||||
if (isAble === false) {
|
if (isAble === false) {
|
||||||
|
@ -173,6 +167,10 @@ const videosUpdateValidator = [
|
||||||
.optional()
|
.optional()
|
||||||
.toBoolean()
|
.toBoolean()
|
||||||
.custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
|
.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) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking videosUpdate parameters', { parameters: req.body })
|
logger.debug('Checking videosUpdate parameters', { parameters: req.body })
|
||||||
|
@ -184,7 +182,8 @@ const videosUpdateValidator = [
|
||||||
const video = res.locals.video
|
const video = res.locals.video
|
||||||
|
|
||||||
// Check if the user who did the request is able to update the 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) {
|
if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
|
||||||
return res.status(409)
|
return res.status(409)
|
||||||
|
@ -192,6 +191,8 @@ const videosUpdateValidator = [
|
||||||
.end()
|
.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user.Account.id, res)) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -98,6 +98,15 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
static loadActorsByShare (videoId: number, t: Sequelize.Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
|
|
|
@ -130,11 +130,27 @@ enum ScopeNames {
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoChannelInclude = {
|
const videoChannelInclude = {
|
||||||
attributes: [ 'name', 'description' ],
|
attributes: [ 'name', 'description', 'id' ],
|
||||||
model: VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
where: {},
|
where: {},
|
||||||
include: [
|
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
|
accountInclude
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -771,12 +787,17 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
preferredUsername: Sequelize.where(Sequelize.col('preferredUsername'), {
|
preferredUsernameChannel: Sequelize.where(Sequelize.col('VideoChannel->Actor.preferredUsername'), {
|
||||||
[ Sequelize.Op.iLike ]: '%' + value + '%'
|
[ 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 + '%'
|
[ Sequelize.Op.iLike ]: '%' + value + '%'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1043,6 +1064,7 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
|
|
||||||
toFormattedJSON (): Video {
|
toFormattedJSON (): Video {
|
||||||
const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
|
const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
|
||||||
|
const formattedVideoChannel = this.VideoChannel.toFormattedJSON()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -1085,6 +1107,15 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
url: formattedAccount.url,
|
url: formattedAccount.url,
|
||||||
host: formattedAccount.host,
|
host: formattedAccount.host,
|
||||||
avatar: formattedAccount.avatar
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { User } from '../../../../shared/index'
|
import { User, Video } from '../../../../shared/index'
|
||||||
import { doubleFollow, flushAndRunMultipleServers, getVideoChannelVideos, uploadVideo, wait } from '../../utils'
|
import { doubleFollow, flushAndRunMultipleServers, getVideoChannelVideos, updateVideo, uploadVideo, wait } from '../../utils'
|
||||||
import {
|
import {
|
||||||
addVideoChannel,
|
addVideoChannel,
|
||||||
deleteVideoChannel,
|
deleteVideoChannel,
|
||||||
|
@ -25,8 +25,11 @@ describe('Test video channels', function () {
|
||||||
let servers: ServerInfo[]
|
let servers: ServerInfo[]
|
||||||
let userInfo: User
|
let userInfo: User
|
||||||
let accountUUID: string
|
let accountUUID: string
|
||||||
let videoChannelId: number
|
let firstVideoChannelId: number
|
||||||
let videoChannelUUID: string
|
let firstVideoChannelUUID: string
|
||||||
|
let secondVideoChannelId: number
|
||||||
|
let secondVideoChannelUUID: string
|
||||||
|
let videoUUID: string
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
@ -42,6 +45,9 @@ describe('Test video channels', function () {
|
||||||
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
|
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
|
||||||
const user: User = res.body
|
const user: User = res.body
|
||||||
accountUUID = user.account.uuid
|
accountUUID = user.account.uuid
|
||||||
|
|
||||||
|
firstVideoChannelId = user.videoChannels[0].id
|
||||||
|
firstVideoChannelUUID = user.videoChannels[0].uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
await wait(5000)
|
await wait(5000)
|
||||||
|
@ -58,17 +64,22 @@ describe('Test video channels', function () {
|
||||||
it('Should create another video channel', async function () {
|
it('Should create another video channel', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
const videoChannel = {
|
{
|
||||||
displayName: 'second video channel',
|
const videoChannel = {
|
||||||
description: 'super video channel description',
|
displayName: 'second video channel',
|
||||||
support: 'super video channel support text'
|
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
|
// 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)
|
await wait(3000)
|
||||||
})
|
})
|
||||||
|
@ -130,7 +141,7 @@ describe('Test video channels', function () {
|
||||||
support: 'video channel support text updated'
|
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)
|
await wait(3000)
|
||||||
})
|
})
|
||||||
|
@ -149,7 +160,7 @@ describe('Test video channels', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get video channel', async function () {
|
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
|
const videoChannel = res.body
|
||||||
expect(videoChannel.displayName).to.equal('video channel updated')
|
expect(videoChannel.displayName).to.equal('video channel updated')
|
||||||
|
@ -157,20 +168,45 @@ describe('Test video channels', function () {
|
||||||
expect(videoChannel.support).to.equal('video channel support text 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)
|
this.timeout(10000)
|
||||||
|
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
const res = await getVideoChannelVideos(server.url, server.accessToken, videoChannelUUID, 0, 5)
|
const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondVideoChannelUUID, 0, 5)
|
||||||
expect(res.body.total).to.equal(1)
|
expect(res1.body.total).to.equal(1)
|
||||||
expect(res.body.data).to.be.an('array')
|
expect(res1.body.data).to.be.an('array')
|
||||||
expect(res.body.data).to.have.lengthOf(1)
|
expect(res1.body.data).to.have.lengthOf(1)
|
||||||
expect(res.body.data[0].name).to.equal('my video name')
|
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 () {
|
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 () {
|
it('Should have video channel deleted', async function () {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
testImage
|
testImage
|
||||||
} from '../'
|
} from '../'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
import { readdirPromise } from '../../../helpers/core-utils'
|
import { readdirPromise } from '../../../helpers/core-utils'
|
||||||
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
|
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
|
||||||
import { dateIsValid, webtorrentAdd } from '../index'
|
import { dateIsValid, webtorrentAdd } from '../index'
|
||||||
|
@ -385,6 +385,7 @@ function updateVideo (url: string, accessToken: string, id: number | string, att
|
||||||
if (attributes.description) body['description'] = attributes.description
|
if (attributes.description) body['description'] = attributes.description
|
||||||
if (attributes.tags) body['tags'] = attributes.tags
|
if (attributes.tags) body['tags'] = attributes.tags
|
||||||
if (attributes.privacy) body['privacy'] = attributes.privacy
|
if (attributes.privacy) body['privacy'] = attributes.privacy
|
||||||
|
if (attributes.channelId) body['channelId'] = attributes.channelId
|
||||||
|
|
||||||
// Upload request
|
// Upload request
|
||||||
if (attributes.thumbnailfile || attributes.previewfile) {
|
if (attributes.thumbnailfile || attributes.previewfile) {
|
||||||
|
@ -489,6 +490,8 @@ async function completeVideoCheck (
|
||||||
expect(video.account.uuid).to.be.a('string')
|
expect(video.account.uuid).to.be.a('string')
|
||||||
expect(video.account.host).to.equal(attributes.account.host)
|
expect(video.account.host).to.equal(attributes.account.host)
|
||||||
expect(video.account.name).to.equal(attributes.account.name)
|
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.likes).to.equal(attributes.likes)
|
||||||
expect(video.dislikes).to.equal(attributes.dislikes)
|
expect(video.dislikes).to.equal(attributes.dislikes)
|
||||||
expect(video.isLocal).to.equal(attributes.isLocal)
|
expect(video.isLocal).to.equal(attributes.isLocal)
|
||||||
|
@ -498,19 +501,19 @@ async function completeVideoCheck (
|
||||||
expect(dateIsValid(video.updatedAt)).to.be.true
|
expect(dateIsValid(video.updatedAt)).to.be.true
|
||||||
|
|
||||||
const res = await getVideo(url, video.uuid)
|
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.files).to.have.lengthOf(attributes.files.length)
|
||||||
expect(videoDetails.tags).to.deep.equal(attributes.tags)
|
expect(videoDetails.tags).to.deep.equal(attributes.tags)
|
||||||
expect(videoDetails.account.name).to.equal(attributes.account.name)
|
expect(videoDetails.account.name).to.equal(attributes.account.name)
|
||||||
expect(videoDetails.account.host).to.equal(attributes.account.host)
|
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.displayName).to.equal(attributes.channel.name)
|
||||||
expect(videoDetails.channel.name).to.have.lengthOf(36)
|
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(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
|
||||||
expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
|
expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
|
||||||
expect(dateIsValid(videoDetails.channel.updatedAt)).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) {
|
for (const attributeFile of attributes.files) {
|
||||||
const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
|
const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
|
||||||
|
|
|
@ -64,7 +64,7 @@ export interface ActivityAnnounce extends BaseActivity {
|
||||||
|
|
||||||
export interface ActivityUndo extends BaseActivity {
|
export interface ActivityUndo extends BaseActivity {
|
||||||
type: 'Undo',
|
type: 'Undo',
|
||||||
object: ActivityFollow | ActivityLike | ActivityCreate
|
object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActivityLike extends BaseActivity {
|
export interface ActivityLike extends BaseActivity {
|
||||||
|
|
|
@ -8,7 +8,7 @@ export interface Actor {
|
||||||
host: string
|
host: string
|
||||||
followingCount: number
|
followingCount: number
|
||||||
followersCount: number
|
followersCount: number
|
||||||
createdAt: Date
|
createdAt: Date | string
|
||||||
updatedAt: Date
|
updatedAt: Date | string
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export interface VideoUpdate {
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
commentsEnabled?: boolean
|
commentsEnabled?: boolean
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
|
channelId?: number
|
||||||
thumbnailfile?: Blob
|
thumbnailfile?: Blob
|
||||||
previewfile?: Blob
|
previewfile?: Blob
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,16 @@ export interface Video {
|
||||||
host: string
|
host: string
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel: {
|
||||||
|
id: number
|
||||||
|
uuid: string
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
url: string
|
||||||
|
host: string
|
||||||
|
avatar: Avatar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoDetails extends Video {
|
export interface VideoDetails extends Video {
|
||||||
|
|
Loading…
Reference in New Issue