const query = event.query
this.userService.autocomplete(query)
.subscribe(
- (usernames) => {
+ usernames => {
this.usernamePropositions = usernames
},
this.videoOwnershipService
.changeOwnership(this.video.id, username)
.subscribe(
- () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership changed.')),
+ () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership change request sent.')),
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
- const resultList = await UserModel.autocomplete(req.query.search as string)
+ const resultList = await UserModel.autoComplete(req.query.search as string)
return res.json(resultList)
}
import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video'
import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
-import { VideoChangeOwnershipStatus } from '../../../../shared/models/videos'
+import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { getFormattedObjects } from '../../../helpers/utils'
+import { changeVideoChannelShare } from '../../../lib/activitypub'
+import { sendUpdateVideo } from '../../../lib/activitypub/send'
const ownershipVideoRouter = express.Router()
const initiatorAccount = res.locals.oauth.token.User.Account as AccountModel
const nextOwner = res.locals.nextOwner as AccountModel
- await sequelizeTypescript.transaction(async t => {
- await VideoChangeOwnershipModel.findOrCreate({
+ await sequelizeTypescript.transaction(t => {
+ return VideoChangeOwnershipModel.findOrCreate({
where: {
initiatorAccountId: initiatorAccount.id,
nextOwnerAccountId: nextOwner.id,
nextOwnerAccountId: nextOwner.id,
videoId: videoInstance.id,
status: VideoChangeOwnershipStatus.WAITING
- }
+ },
+ transaction: t
})
- logger.info('Ownership change for video %s created.', videoInstance.name)
- return res.type('json').status(204).end()
+
})
+
+ logger.info('Ownership change for video %s created.', videoInstance.name)
+ return res.type('json').status(204).end()
}
async function listVideoOwnership (req: express.Request, res: express.Response) {
const targetVideo = videoChangeOwnership.Video
const channel = res.locals.videoChannel as VideoChannelModel
+ const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId)
+
targetVideo.set('channelId', channel.id)
+ const targetVideoUpdated = await targetVideo.save({ transaction: t })
+ targetVideoUpdated.VideoChannel = channel
+
+ if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) {
+ await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
+ await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
+ }
- await targetVideo.save()
videoChangeOwnership.set('status', VideoChangeOwnershipStatus.ACCEPTED)
- await videoChangeOwnership.save()
+ await videoChangeOwnership.save({ transaction: t })
return res.sendStatus(204)
})
async function refuseOwnership (req: express.Request, res: express.Response) {
return sequelizeTypescript.transaction(async t => {
const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
+
videoChangeOwnership.set('status', VideoChangeOwnershipStatus.REFUSED)
- await videoChangeOwnership.save()
+ await videoChangeOwnership.save({ transaction: t })
+
return res.sendStatus(204)
})
}
import { isActorFollowActivityValid } from './actor'
import { isBaseActivityValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
+import { isAnnounceActivityValid } from './announce'
function isUndoActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Undo') &&
(
isActorFollowActivityValid(activity.object) ||
isLikeActivityValid(activity.object) ||
- isDislikeActivityValid(activity.object)
+ isDislikeActivityValid(activity.object) ||
+ isAnnounceActivityValid(activity.object)
)
}
function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
return sequelizeTypescript.transaction(async t => {
- const byAccount = await AccountModel.loadByUrl(actorUrl, t)
- if (!byAccount) throw new Error('Unknown account ' + actorUrl)
+ const byActor = await ActorModel.loadByUrl(actorUrl, t)
+ if (!byActor) throw new Error('Unknown actor ' + actorUrl)
const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
- if (!share) throw new Error(`'Unknown video share ${announceActivity.id}.`)
+ if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
+
+ if (share.actorId !== byActor.id) throw new Error(`${share.url} is not shared by ${byActor.url}.`)
await share.destroy({ transaction: t })
if (share.Video.isOwned()) {
// Don't resend the activity to the sender
- const exceptions = [ byAccount.Actor ]
+ const exceptions = [ byActor ]
await forwardVideoRelatedActivity(announceActivity, t, exceptions, share.Video)
}
logger.info('Creating job to send announce %s.', videoShare.url)
- return broadcastToFollowers(data, byActor, [ byActor ], t)
+ const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
+ const followersException = [ byActor ]
+
+ return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
}
function announceActivityData (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce {
import { broadcastToFollowers } from './utils'
import { audiencify, getAudience } from '../audience'
import { logger } from '../../../helpers/logger'
+import { videoFeedsValidator } from '../../../middlewares/validators'
+import { VideoCaptionModel } from '../../../models/video/video-caption'
-async function sendUpdateVideo (video: VideoModel, t: Transaction) {
+async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
logger.info('Creating job to update video %s.', video.url)
- const byActor = video.VideoChannel.Account.Actor
+ const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor
const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
+
+ // Needed to build the AP object
+ if (!video.VideoCaptions) video.VideoCaptions = await video.$get('VideoCaptions') as VideoCaptionModel[]
+
const videoObject = video.toActivityPubObject()
const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
}
async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+ logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
+
await undoShareByVideoChannel(video, oldVideoChannel, t)
await shareByVideoChannel(video, t)
isUserAutoPlayVideoValid,
isUserBlockedReasonValid,
isUserBlockedValid,
- isUserNSFWPolicyValid,
isUserEmailVerifiedValid,
+ isUserNSFWPolicyValid,
isUserPasswordValid,
isUserRoleValid,
isUserUsernameValid,
- isUserVideoQuotaValid,
- isUserVideoQuotaDailyValid
+ isUserVideoQuotaDailyValid,
+ isUserVideoQuotaValid
} from '../../helpers/custom-validators/users'
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
import { OAuthTokenModel } from '../oauth/oauth-token'
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
import { values } from 'lodash'
import { NSFW_POLICY_TYPES } from '../../initializers'
-import { VideoFileModel } from '../video/video-file'
enum ScopeNames {
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
}
}
+ static autoComplete (search: string) {
+ const query = {
+ where: {
+ username: {
+ [ Sequelize.Op.like ]: `%${search}%`
+ }
+ },
+ limit: 10
+ }
+
+ return UserModel.findAll(query)
+ .then(u => u.map(u => u.username))
+ }
+
hasRight (right: UserRight) {
return hasUserRight(this.role, right)
}
return parseInt(total, 10)
})
}
-
- static autocomplete (search: string) {
- return UserModel.findAll({
- where: {
- username: {
- [Sequelize.Op.like]: `%${search}%`
- }
- }
- })
- .then(u => u.map(u => u.username))
- }
}
{
model: () => VideoModel,
required: true,
- include: [{ model: () => VideoFileModel }]
+ include: [
+ { model: () => VideoFileModel }
+ ]
}
]
}
Video: VideoModel
static listForApi (nextOwnerId: number, start: number, count: number, sort: string) {
- return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll({
+ const query = {
offset: start,
limit: count,
order: getSort(sort),
where: {
nextOwnerAccountId: nextOwnerId
}
- })
- .then(({ rows, count }) => ({ total: count, data: rows }))
+ }
+
+ return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll(query)
+ .then(({ rows, count }) => ({ total: count, data: rows }))
}
static load (id: number) {
})
}
+ static loadByIdAndPopulateAccount (id: number) {
+ return VideoChannelModel.unscoped()
+ .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
+ .findById(id)
+ }
+
static loadByIdAndAccount (id: number, accountId: number) {
const query = {
where: {
}
}
- return VideoChannelModel
+ return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findOne(query)
}
static loadAndPopulateAccount (id: number) {
- return VideoChannelModel
+ return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findById(id)
}
]
}
- return VideoChannelModel
+ return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findOne(query)
}
]
}
- return VideoChannelModel
+ return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findOne(query)
}
]
}
- return VideoChannelModel
+ return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
.findById(id, options)
}
import './video-blacklist'
import './video-blacklist-management'
import './video-captions'
+import './vidoe-change-ownership'
import './video-channels'
import './video-comments'
import './video-description'
import {
acceptChangeOwnership,
changeVideoOwnership,
- createUser,
+ createUser, doubleFollow, flushAndRunMultipleServers,
flushTests,
getMyUserInformation,
getVideoChangeOwnershipList,
ServerInfo,
setAccessTokensToServers,
uploadVideo,
- userLogin
+ userLogin,
+ getVideo
} from '../../utils'
import { waitJobs } from '../../utils/server/jobs'
import { User } from '../../../../shared/models/users'
+import { VideoDetails } from '../../../../shared/models/videos'
const expect = chai.expect
describe('Test video change ownership - nominal', function () {
- let server: ServerInfo = undefined
+ let servers: ServerInfo[] = []
const firstUser = {
username: 'first',
password: 'My great password'
before(async function () {
this.timeout(50000)
- // Run one server
- await flushTests()
- server = await runServer(1)
- await setAccessTokensToServers([server])
+ servers = await flushAndRunMultipleServers(2)
+ await setAccessTokensToServers(servers)
const videoQuota = 42000000
- await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota)
- await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, videoQuota)
+ await createUser(servers[0].url, servers[0].accessToken, firstUser.username, firstUser.password, videoQuota)
+ await createUser(servers[0].url, servers[0].accessToken, secondUser.username, secondUser.password, videoQuota)
- firstUserAccessToken = await userLogin(server, firstUser)
- secondUserAccessToken = await userLogin(server, secondUser)
+ firstUserAccessToken = await userLogin(servers[0], firstUser)
+ secondUserAccessToken = await userLogin(servers[0], secondUser)
- // Upload some videos on the server
- const video1Attributes = {
+ const videoAttributes = {
name: 'my super name',
description: 'my super description'
}
- await uploadVideo(server.url, firstUserAccessToken, video1Attributes)
+ await uploadVideo(servers[0].url, firstUserAccessToken, videoAttributes)
- await waitJobs(server)
+ await waitJobs(servers)
- const res = await getVideosList(server.url)
+ const res = await getVideosList(servers[0].url)
const videos = res.body.data
expect(videos.length).to.equal(1)
- server.video = videos.find(video => video.name === 'my super name')
+ const video = videos.find(video => video.name === 'my super name')
+ expect(video.channel.name).to.equal('first_channel')
+ servers[0].video = video
+
+ await doubleFollow(servers[0], servers[1])
})
it('Should not have video change ownership', async function () {
- const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+ const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
expect(resFirstUser.body.total).to.equal(0)
expect(resFirstUser.body.data).to.be.an('array')
expect(resFirstUser.body.data.length).to.equal(0)
- const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+ const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
expect(resSecondUser.body.total).to.equal(0)
expect(resSecondUser.body.data).to.be.an('array')
it('Should send a request to change ownership of a video', async function () {
this.timeout(15000)
- await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+ await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
})
it('Should only return a request to change ownership for the second user', async function () {
- const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+ const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
expect(resFirstUser.body.total).to.equal(0)
expect(resFirstUser.body.data).to.be.an('array')
expect(resFirstUser.body.data.length).to.equal(0)
- const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+ const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
expect(resSecondUser.body.total).to.equal(1)
expect(resSecondUser.body.data).to.be.an('array')
it('Should accept the same change ownership request without crashing', async function () {
this.timeout(10000)
- await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+ await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
})
it('Should not create multiple change ownership requests while one is waiting', async function () {
this.timeout(10000)
- const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+ const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
expect(resSecondUser.body.total).to.equal(1)
expect(resSecondUser.body.data).to.be.an('array')
it('Should not be possible to refuse the change of ownership from first user', async function () {
this.timeout(10000)
- await refuseChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, 403)
+ await refuseChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, 403)
})
it('Should be possible to refuse the change of ownership from second user', async function () {
this.timeout(10000)
- await refuseChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId)
+ await refuseChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId)
})
it('Should send a new request to change ownership of a video', async function () {
this.timeout(15000)
- await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+ await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
})
it('Should return two requests to change ownership for the second user', async function () {
- const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+ const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
expect(resFirstUser.body.total).to.equal(0)
expect(resFirstUser.body.data).to.be.an('array')
expect(resFirstUser.body.data.length).to.equal(0)
- const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+ const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
expect(resSecondUser.body.total).to.equal(2)
expect(resSecondUser.body.data).to.be.an('array')
it('Should not be possible to accept the change of ownership from first user', async function () {
this.timeout(10000)
- const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
+ const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
const secondUserInformation: User = secondUserInformationResponse.body
const channelId = secondUserInformation.videoChannels[0].id
- await acceptChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
+ await acceptChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
})
it('Should be possible to accept the change of ownership from second user', async function () {
this.timeout(10000)
- const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
+ const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
const secondUserInformation: User = secondUserInformationResponse.body
const channelId = secondUserInformation.videoChannels[0].id
- await acceptChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
+ await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
+
+ await waitJobs(servers)
+ })
+
+ it('Should have video channel updated', async function () {
+ for (const server of servers) {
+ const res = await getVideo(server.url, servers[0].video.uuid)
+
+ const video: VideoDetails = res.body
+
+ expect(video.name).to.equal('my super name')
+ expect(video.channel.displayName).to.equal('Main second channel')
+ expect(video.channel.name).to.equal('second_channel')
+ }
})
after(async function () {
- killallServers([server])
+ killallServers(servers)
})
})