Begin moving video channel to actor
This commit is contained in:
parent
fadf619ad6
commit
50d6de9c28
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
|
|||
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
import { SortMeta } from 'primeng/primeng'
|
||||
import { AccountFollow } from '../../../../../../shared/models/accounts/follow.model'
|
||||
import { AccountFollow } from '../../../../../../shared/models/actors/follow.model'
|
||||
import { RestPagination, RestTable } from '../../../shared'
|
||||
import { FollowService } from '../shared'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component } from '@angular/core'
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
import { SortMeta } from 'primeng/primeng'
|
||||
import { AccountFollow } from '../../../../../../shared/models/accounts/follow.model'
|
||||
import { AccountFollow } from '../../../../../../shared/models/actors/follow.model'
|
||||
import { ConfirmService } from '../../../core/confirm/confirm.service'
|
||||
import { RestPagination, RestTable } from '../../../shared'
|
||||
import { FollowService } from '../shared'
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Observable } from 'rxjs/Observable'
|
|||
import { ReplaySubject } from 'rxjs/ReplaySubject'
|
||||
import { Subject } from 'rxjs/Subject'
|
||||
import { OAuthClientLocal, User as UserServerModel, UserRefreshToken, UserRole, VideoChannel } from '../../../../../shared'
|
||||
import { Account } from '../../../../../shared/models/accounts'
|
||||
import { Account } from '../../../../../shared/models/actors'
|
||||
import { UserLogin } from '../../../../../shared/models/users/user-login.model'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { RestExtractor } from '../../shared/rest'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Account as ServerAccount } from '../../../../../shared/models/accounts/account.model'
|
||||
import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model'
|
||||
import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
|
||||
import { environment } from '../../../environments/environment'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Account } from '../../../../../shared/models/accounts'
|
||||
import { Account } from '../../../../../shared/models/actors'
|
||||
import { Video } from '../../shared/video/video.model'
|
||||
import { AuthUser } from '../../core'
|
||||
import {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { User } from '../'
|
||||
import { Video as VideoServerModel } from '../../../../../shared'
|
||||
import { Account } from '../../../../../shared/models/accounts'
|
||||
import { Account } from '../../../../../shared/models/actors'
|
||||
import { environment } from '../../../environments/environment'
|
||||
|
||||
export class Video implements VideoServerModel {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { getServerAccount } from '../server/helpers'
|
||||
import { getServerActor } from '../server/helpers'
|
||||
import { initDatabaseModels } from '../server/initializers'
|
||||
import { AccountFollowModel } from '../server/models/account/account-follow'
|
||||
import { ActorFollowModel } from '../server/models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../server/models/video/video'
|
||||
|
||||
initDatabaseModels(true)
|
||||
.then(() => {
|
||||
return getServerAccount()
|
||||
return getServerActor()
|
||||
})
|
||||
.then(serverAccount => {
|
||||
return AccountFollowModel.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined)
|
||||
return ActorFollowModel.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined)
|
||||
})
|
||||
.then(res => {
|
||||
return res.total > 0
|
||||
|
|
|
@ -50,7 +50,8 @@ migrate()
|
|||
|
||||
// ----------- PeerTube modules -----------
|
||||
import { installApplication } from './server/initializers'
|
||||
import { activitypubHttpJobScheduler, transcodingJobScheduler, VideosPreviewCache } from './server/lib'
|
||||
import { activitypubHttpJobScheduler, transcodingJobScheduler } from './server/lib/jobs'
|
||||
import { VideosPreviewCache } from './server/lib/cache'
|
||||
import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers'
|
||||
|
||||
// ----------- Command line -----------
|
||||
|
|
|
@ -2,20 +2,13 @@
|
|||
import * as express from 'express'
|
||||
import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers'
|
||||
import { ACTIVITY_PUB, CONFIG } from '../../initializers'
|
||||
import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send'
|
||||
import { buildVideoAnnounceToFollowers } from '../../lib/index'
|
||||
import { buildVideoAnnounceToFollowers } from '../../lib/activitypub/send'
|
||||
import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares'
|
||||
import {
|
||||
videoChannelsGetValidator,
|
||||
videoChannelsShareValidator,
|
||||
videosGetValidator,
|
||||
videosShareValidator
|
||||
} from '../../middlewares/validators'
|
||||
import { videoChannelsGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { AccountFollowModel } from '../../models/account/account-follow'
|
||||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../models/video/video-share'
|
||||
|
||||
const activityPubClientRouter = express.Router()
|
||||
|
@ -50,11 +43,6 @@ activityPubClientRouter.get('/video-channels/:id',
|
|||
executeIfActivityPub(asyncMiddleware(videoChannelController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/video-channels/:id/announces/:accountId',
|
||||
executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)),
|
||||
executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController))
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -75,7 +63,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
|
|||
const page = req.query.page || 1
|
||||
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
|
||||
|
||||
const result = await AccountFollowModel.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count)
|
||||
const result = await ActorFollowModel.listAcceptedFollowerUrlsForApi([ account.Actor.id ], undefined, start, count)
|
||||
const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
|
||||
|
||||
return res.json(activityPubResult)
|
||||
|
@ -87,7 +75,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
|
|||
const page = req.query.page || 1
|
||||
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
|
||||
|
||||
const result = await AccountFollowModel.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count)
|
||||
const result = await ActorFollowModel.listAcceptedFollowingUrlsForApi([ account.Actor.id ], undefined, start, count)
|
||||
const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
|
||||
|
||||
return res.json(activityPubResult)
|
||||
|
@ -101,14 +89,7 @@ function videoController (req: express.Request, res: express.Response, next: exp
|
|||
|
||||
async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const share = res.locals.videoShare as VideoShareModel
|
||||
const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined)
|
||||
|
||||
return res.json(object)
|
||||
}
|
||||
|
||||
async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const share = res.locals.videoChannelShare as VideoChannelShareModel
|
||||
const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined)
|
||||
const object = await buildVideoAnnounceToFollowers(share.Actor, res.locals.video, undefined)
|
||||
|
||||
return res.json(object)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { isActivityValid } from '../../helpers/custom-validators/activitypub/act
|
|||
import { processActivities } from '../../lib/activitypub/process/process'
|
||||
import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
|
||||
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
|
||||
const inboxRouter = express.Router()
|
||||
|
||||
|
@ -48,7 +49,14 @@ async function inboxController (req: express.Request, res: express.Response, nex
|
|||
activities = activities.filter(a => isActivityValid(a))
|
||||
logger.debug('We keep %d activities.', activities.length, { activities })
|
||||
|
||||
await processActivities(activities, res.locals.signature.account, res.locals.account)
|
||||
let specificActor: ActorModel = undefined
|
||||
if (res.locals.account) {
|
||||
specificActor = res.locals.account
|
||||
} else if (res.locals.videoChannel) {
|
||||
specificActor = res.locals.videoChannel
|
||||
}
|
||||
|
||||
await processActivities(activities, res.locals.signature.actor, specificActor)
|
||||
|
||||
res.status(204).end()
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@ import { Activity } from '../../../shared/models/activitypub/activity'
|
|||
import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
||||
import { pageToStartAndCount } from '../../helpers/core-utils'
|
||||
import { ACTIVITY_PUB } from '../../initializers/constants'
|
||||
import { addActivityData } from '../../lib/activitypub/send/send-add'
|
||||
import { announceActivityData, createActivityData } from '../../lib/activitypub/send'
|
||||
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
|
||||
import { announceActivityData } from '../../lib/index'
|
||||
import { asyncMiddleware, localAccountValidator } from '../../middlewares'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
|
@ -27,29 +26,30 @@ export {
|
|||
|
||||
async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const account: AccountModel = res.locals.account
|
||||
const actor = account.Actor
|
||||
|
||||
const page = req.query.page || 1
|
||||
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
|
||||
|
||||
const data = await VideoModel.listAllAndSharedByAccountForOutbox(account.id, start, count)
|
||||
const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
|
||||
const activities: Activity[] = []
|
||||
|
||||
for (const video of data.data) {
|
||||
const videoObject = video.toActivityPubObject()
|
||||
|
||||
// This is a shared video
|
||||
const videoChannel = video.VideoChannel
|
||||
// This is a shared video
|
||||
if (video.VideoShares !== undefined && video.VideoShares.length !== 0) {
|
||||
const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.Actor.url, videoObject, undefined)
|
||||
const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
|
||||
|
||||
const url = getAnnounceActivityPubUrl(video.url, account)
|
||||
const announceActivity = await announceActivityData(url, account, addActivity, undefined)
|
||||
const url = getAnnounceActivityPubUrl(video.url, actor)
|
||||
const announceActivity = await announceActivityData(url, actor, createActivity, undefined)
|
||||
|
||||
activities.push(announceActivity)
|
||||
} else {
|
||||
const addActivity = await addActivityData(video.url, account, video, videoChannel.Actor.url, videoObject, undefined)
|
||||
const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
|
||||
|
||||
activities.push(addActivity)
|
||||
activities.push(createActivity)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ async function outboxController (req: express.Request, res: express.Response, ne
|
|||
data: activities,
|
||||
total: data.total
|
||||
}
|
||||
const json = activityPubCollectionPagination(account.url + '/outbox', page, newResult)
|
||||
const json = activityPubCollectionPagination(account.Actor.url + '/outbox', page, newResult)
|
||||
|
||||
return res.json(json).end()
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as express from 'express'
|
||||
import { UserRight } from '../../../../shared/models/users'
|
||||
import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers'
|
||||
import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub'
|
||||
import { sendUndoFollow } from '../../../lib/activitypub/send'
|
||||
import { sendFollow } from '../../../lib/index'
|
||||
import { getFormattedObjects, getServerActor, loadActorUrlOrGetFromWebfinger, logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers'
|
||||
import { getOrCreateActorAndServerAndModel } from '../../../lib/activitypub'
|
||||
import { sendFollow, sendUndoFollow } from '../../../lib/activitypub/send'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
|
@ -17,8 +16,8 @@ import {
|
|||
setPagination
|
||||
} from '../../../middlewares'
|
||||
import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
|
||||
const serverFollowsRouter = express.Router()
|
||||
|
||||
|
@ -38,7 +37,7 @@ serverFollowsRouter.post('/following',
|
|||
asyncMiddleware(followRetry)
|
||||
)
|
||||
|
||||
serverFollowsRouter.delete('/following/:accountId',
|
||||
serverFollowsRouter.delete('/following/:host',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
|
||||
asyncMiddleware(removeFollowingValidator),
|
||||
|
@ -62,43 +61,41 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const serverAccount = await getServerAccount()
|
||||
const resultList = await AccountFollowModel.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort)
|
||||
const serverActor = await getServerActor()
|
||||
const resultList = await ActorFollowModel.listFollowingForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const serverAccount = await getServerAccount()
|
||||
const resultList = await AccountFollowModel.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort)
|
||||
const serverActor = await getServerActor()
|
||||
const resultList = await ActorFollowModel.listFollowersForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function followRetry (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const hosts = req.body.hosts as string[]
|
||||
const fromAccount = await getServerAccount()
|
||||
const fromActor = await getServerActor()
|
||||
|
||||
const tasks: Promise<any>[] = []
|
||||
const accountName = SERVER_ACCOUNT_NAME
|
||||
const actorName = SERVER_ACTOR_NAME
|
||||
|
||||
for (const host of hosts) {
|
||||
|
||||
// We process each host in a specific transaction
|
||||
// First, we add the follow request in the database
|
||||
// Then we send the follow request to other account
|
||||
const p = loadLocalOrGetAccountFromWebfinger(accountName, host)
|
||||
.then(accountResult => {
|
||||
let targetAccount = accountResult.account
|
||||
|
||||
// Then we send the follow request to other actor
|
||||
const p = loadActorUrlOrGetFromWebfinger(actorName, host)
|
||||
.then(actorUrl => getOrCreateActorAndServerAndModel(actorUrl))
|
||||
.then(targetActor => {
|
||||
const options = {
|
||||
arguments: [ fromAccount, targetAccount, accountResult.loadedFromDB ],
|
||||
arguments: [ fromActor, targetActor ],
|
||||
errorMessage: 'Cannot follow with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(follow, options)
|
||||
})
|
||||
.catch(err => logger.warn('Cannot follow server %s.', `${accountName}@${host}`, err))
|
||||
.catch(err => logger.warn('Cannot follow server %s.', host, err))
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
@ -110,42 +107,32 @@ async function followRetry (req: express.Request, res: express.Response, next: e
|
|||
return res.status(204).end()
|
||||
}
|
||||
|
||||
async function follow (fromAccount: AccountModel, targetAccount: AccountModel, targetAlreadyInDB: boolean) {
|
||||
try {
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (targetAlreadyInDB === false) {
|
||||
await saveAccountAndServerIfNotExist(targetAccount, t)
|
||||
}
|
||||
|
||||
const [ accountFollow ] = await AccountFollowModel.findOrCreate({
|
||||
where: {
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id
|
||||
},
|
||||
defaults: {
|
||||
state: 'pending',
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
accountFollow.AccountFollowing = targetAccount
|
||||
accountFollow.AccountFollower = fromAccount
|
||||
|
||||
// Send a notification to remote server
|
||||
if (accountFollow.state === 'pending') {
|
||||
await sendFollow(accountFollow, t)
|
||||
}
|
||||
function follow (fromActor: ActorModel, targetActor: ActorModel) {
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const [ actorFollow ] = await ActorFollowModel.findOrCreate({
|
||||
where: {
|
||||
actorId: fromActor.id,
|
||||
targetActorId: targetActor.id
|
||||
},
|
||||
defaults: {
|
||||
state: 'pending',
|
||||
actorId: fromActor.id,
|
||||
targetActorId: targetActor.id
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
} catch (err) {
|
||||
// Reset target account
|
||||
targetAccount.isNewRecord = !targetAlreadyInDB
|
||||
throw err
|
||||
}
|
||||
actorFollow.ActorFollowing = targetActor
|
||||
actorFollow.ActorFollower = fromActor
|
||||
|
||||
// Send a notification to remote server
|
||||
if (actorFollow.state === 'pending') {
|
||||
await sendFollow(actorFollow, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const follow: AccountFollowModel = res.locals.follow
|
||||
const follow: ActorFollowModel = res.locals.follow
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (follow.state === 'accepted') await sendUndoFollow(follow, t)
|
||||
|
@ -153,24 +140,11 @@ async function removeFollow (req: express.Request, res: express.Response, next:
|
|||
await follow.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
// Destroy the account that will destroy video channels, videos and video files too
|
||||
// Destroy the actor that will destroy video channels, videos and video files too
|
||||
// This could be long so don't wait this task
|
||||
const following = follow.AccountFollowing
|
||||
const following = follow.ActorFollowing
|
||||
following.destroy()
|
||||
.catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.Actor.url, err))
|
||||
.catch(err => logger.error('Cannot destroy actor that we do not follow anymore %s.', following.url, err))
|
||||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
||||
async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
|
||||
let loadedFromDB = true
|
||||
let account = await AccountModel.loadByNameAndHost(name, host)
|
||||
|
||||
if (!account) {
|
||||
const nameWithDomain = name + '@' + host
|
||||
account = await getAccountFromWebfinger(nameWithDomain)
|
||||
loadedFromDB = false
|
||||
}
|
||||
|
||||
return { account, loadedFromDB }
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
|||
import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
|
||||
import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers'
|
||||
import { CONFIG } from '../../initializers'
|
||||
import { createUserAccountAndChannel } from '../../lib'
|
||||
import { createUserAccountAndChannel } from '../../lib/user'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import * as express from 'express'
|
||||
import {
|
||||
logger,
|
||||
getFormattedObjects,
|
||||
retryTransactionWrapper
|
||||
} from '../../../helpers'
|
||||
import { UserRight, VideoAbuseCreate } from '../../../../shared'
|
||||
import { getFormattedObjects, logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { sendVideoAbuse } from '../../../lib/activitypub/send'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
videoAbuseReportValidator,
|
||||
videoAbusesSortValidator,
|
||||
setVideoAbusesSort,
|
||||
setPagination,
|
||||
asyncMiddleware
|
||||
setVideoAbusesSort,
|
||||
videoAbuseReportValidator,
|
||||
videoAbusesSortValidator
|
||||
} from '../../../middlewares'
|
||||
import { VideoAbuseCreate, UserRight } from '../../../../shared'
|
||||
import { sendVideoAbuse } from '../../../lib/index'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoAbuseModel } from '../../../models/video/video-abuse'
|
||||
|
@ -80,7 +76,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
|
|||
|
||||
// We send the video abuse to the origin server
|
||||
if (videoInstance.isOwned() === false) {
|
||||
await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t)
|
||||
await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import * as express from 'express'
|
|||
import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
|
||||
import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { createVideoChannel } from '../../../lib'
|
||||
import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
|
||||
import { setAsyncActorKeys } from '../../../lib/activitypub'
|
||||
import { createVideoChannel } from '../../../lib/video-channel'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
|
@ -92,15 +92,17 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R
|
|||
return res.type('json').status(204).end()
|
||||
}
|
||||
|
||||
function addVideoChannel (req: express.Request, res: express.Response) {
|
||||
async function addVideoChannel (req: express.Request, res: express.Response) {
|
||||
const videoChannelInfo: VideoChannelCreate = req.body
|
||||
const account: AccountModel = res.locals.oauth.token.User.Account
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t)
|
||||
|
||||
logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
|
||||
const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
|
||||
return createVideoChannel(videoChannelInfo, account, t)
|
||||
})
|
||||
|
||||
setAsyncActorKeys(videoChannelCreated.Actor)
|
||||
|
||||
logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
|
||||
}
|
||||
|
||||
async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
@ -128,12 +130,13 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
|
|||
if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
|
||||
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
|
||||
|
||||
const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
|
||||
await videoChannelInstance.save(sequelizeOptions)
|
||||
|
||||
await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
|
||||
// TODO
|
||||
// await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
|
||||
})
|
||||
|
||||
logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
|
||||
logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
|
||||
} catch (err) {
|
||||
logger.debug('Cannot update the video channel.', err)
|
||||
|
||||
|
@ -160,11 +163,12 @@ async function removeVideoChannelRetryWrapper (req: express.Request, res: expres
|
|||
async function removeVideoChannel (req: express.Request, res: express.Response) {
|
||||
const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
await videoChannelInstance.destroy({ transaction: t })
|
||||
|
||||
logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
|
||||
})
|
||||
|
||||
logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
|
||||
}
|
||||
|
||||
async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
resetSequelizeInstance,
|
||||
retryTransactionWrapper
|
||||
} from '../../../helpers'
|
||||
import { getServerAccount } from '../../../helpers/utils'
|
||||
import { getServerActor } from '../../../helpers/utils'
|
||||
import {
|
||||
CONFIG,
|
||||
sequelizeTypescript,
|
||||
|
@ -22,8 +22,7 @@ import {
|
|||
VIDEO_PRIVACIES
|
||||
} from '../../../initializers'
|
||||
import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub'
|
||||
import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send'
|
||||
import { sendCreateViewToVideoFollowers } from '../../../lib/index'
|
||||
import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
|
||||
import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
|
@ -248,7 +247,8 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
|
|||
// Don't send video to remote servers, it is private
|
||||
if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated
|
||||
|
||||
await sendAddVideo(video, t)
|
||||
await sendCreateVideo(video, t)
|
||||
// TODO: share by video channel
|
||||
await shareVideoByServer(video, t)
|
||||
|
||||
logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
|
||||
|
@ -304,7 +304,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
|||
|
||||
// Video is not private anymore, send a create action to remote servers
|
||||
if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
|
||||
await sendAddVideo(videoInstanceUpdated, t)
|
||||
await sendCreateVideo(videoInstanceUpdated, t)
|
||||
// TODO: Send by video channel
|
||||
await shareVideoByServer(videoInstanceUpdated, t)
|
||||
}
|
||||
})
|
||||
|
@ -330,7 +331,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
|
|||
const videoInstance = res.locals.video
|
||||
|
||||
await videoInstance.increment('views')
|
||||
const serverAccount = await getServerAccount()
|
||||
const serverAccount = await getServerActor()
|
||||
|
||||
if (videoInstance.isOwned()) {
|
||||
await sendCreateViewToVideoFollowers(serverAccount, videoInstance, undefined)
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import * as express from 'express'
|
||||
import * as cors from 'cors'
|
||||
import {
|
||||
CONFIG,
|
||||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS
|
||||
} from '../initializers'
|
||||
import { VideosPreviewCache } from '../lib'
|
||||
import * as express from 'express'
|
||||
import { CONFIG, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers'
|
||||
import { VideosPreviewCache } from '../lib/cache'
|
||||
import { asyncMiddleware } from '../middlewares'
|
||||
|
||||
const staticRouter = express.Router()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as express from 'express'
|
||||
import { asyncMiddleware } from '../middlewares'
|
||||
import { webfingerValidator } from '../middlewares/validators'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
|
||||
const webfingerRouter = express.Router()
|
||||
|
||||
|
@ -19,16 +19,16 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const account = res.locals.account as AccountModel
|
||||
const actor = res.locals.actor as ActorModel
|
||||
|
||||
const json = {
|
||||
subject: req.query.resource,
|
||||
aliases: [ account.Actor.url ],
|
||||
aliases: [ actor.url ],
|
||||
links: [
|
||||
{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: account.Actor.url
|
||||
href: actor.url
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ResultList } from '../../shared/models'
|
||||
import { Activity } from '../../shared/models/activitypub'
|
||||
import { ACTIVITY_PUB } from '../initializers'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { signObject } from './peertube-crypto'
|
||||
|
||||
function activityPubContextify <T> (data: T) {
|
||||
|
@ -71,10 +71,10 @@ function activityPubCollectionPagination (url: string, page: any, result: Result
|
|||
return orderedCollectionPagination
|
||||
}
|
||||
|
||||
function buildSignedActivity (byAccount: AccountModel, data: Object) {
|
||||
function buildSignedActivity (byActor: ActorModel, data: Object) {
|
||||
const activity = activityPubContextify(data)
|
||||
|
||||
return signObject(byAccount, activity) as Promise<Activity>
|
||||
return signObject(byActor, activity) as Promise<Activity>
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import * as validator from 'validator'
|
||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
||||
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './actor'
|
||||
import { isActorAcceptActivityValid, isActorDeleteActivityValid, isActorFollowActivityValid } from './actor'
|
||||
import { isAnnounceActivityValid } from './announce'
|
||||
import { isActivityPubUrlValid } from './misc'
|
||||
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||
import { isUndoActivityValid } from './undo'
|
||||
import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
|
||||
import { isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
|
||||
import {
|
||||
isVideoFlagValid,
|
||||
isVideoTorrentAddActivityValid,
|
||||
isVideoTorrentCreateActivityValid,
|
||||
isVideoTorrentDeleteActivityValid,
|
||||
isVideoTorrentUpdateActivityValid
|
||||
} from './videos'
|
||||
|
@ -29,7 +29,6 @@ function isRootActivityValid (activity: any) {
|
|||
|
||||
const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
|
||||
Create: checkCreateActivity,
|
||||
Add: checkAddActivity,
|
||||
Update: checkUpdateActivity,
|
||||
Delete: checkDeleteActivity,
|
||||
Follow: checkFollowActivity,
|
||||
|
@ -59,14 +58,10 @@ export {
|
|||
function checkCreateActivity (activity: any) {
|
||||
return isViewActivityValid(activity) ||
|
||||
isDislikeActivityValid(activity) ||
|
||||
isVideoChannelCreateActivityValid(activity) ||
|
||||
isVideoTorrentCreateActivityValid(activity) ||
|
||||
isVideoFlagValid(activity)
|
||||
}
|
||||
|
||||
function checkAddActivity (activity: any) {
|
||||
return isVideoTorrentAddActivityValid(activity)
|
||||
}
|
||||
|
||||
function checkUpdateActivity (activity: any) {
|
||||
return isVideoTorrentUpdateActivityValid(activity) ||
|
||||
isVideoChannelUpdateActivityValid(activity)
|
||||
|
@ -75,15 +70,15 @@ function checkUpdateActivity (activity: any) {
|
|||
function checkDeleteActivity (activity: any) {
|
||||
return isVideoTorrentDeleteActivityValid(activity) ||
|
||||
isVideoChannelDeleteActivityValid(activity) ||
|
||||
isAccountDeleteActivityValid(activity)
|
||||
isActorDeleteActivityValid(activity)
|
||||
}
|
||||
|
||||
function checkFollowActivity (activity: any) {
|
||||
return isAccountFollowActivityValid(activity)
|
||||
return isActorFollowActivityValid(activity)
|
||||
}
|
||||
|
||||
function checkAcceptActivity (activity: any) {
|
||||
return isAccountAcceptActivityValid(activity)
|
||||
return isActorAcceptActivityValid(activity)
|
||||
}
|
||||
|
||||
function checkAnnounceActivity (activity: any) {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { Response } from 'express'
|
||||
import * as validator from 'validator'
|
||||
import { CONSTRAINTS_FIELDS } from '../../../initializers'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { isAccountNameValid } from '../accounts'
|
||||
import { exists, isUUIDValid } from '../misc'
|
||||
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
|
||||
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
|
||||
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
||||
|
||||
function isActorEndpointsObjectValid (endpointObject: any) {
|
||||
return isActivityPubUrlValid(endpointObject.sharedInbox)
|
||||
|
@ -27,7 +31,12 @@ function isActorPublicKeyValid (publicKey: string) {
|
|||
}
|
||||
|
||||
function isActorPreferredUsernameValid (preferredUsername: string) {
|
||||
return isAccountNameValid(preferredUsername)
|
||||
return isAccountNameValid(preferredUsername) || isVideoChannelNameValid(preferredUsername)
|
||||
}
|
||||
|
||||
const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
|
||||
function isActorNameValid (name: string) {
|
||||
return exists(name) && validator.matches(name, actorNameRegExp)
|
||||
}
|
||||
|
||||
function isActorPrivateKeyValid (privateKey: string) {
|
||||
|
@ -46,10 +55,16 @@ function isRemoteActorValid (remoteActor: any) {
|
|||
isActivityPubUrlValid(remoteActor.followers) &&
|
||||
isActivityPubUrlValid(remoteActor.inbox) &&
|
||||
isActivityPubUrlValid(remoteActor.outbox) &&
|
||||
isActorNameValid(remoteActor.name) &&
|
||||
isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
|
||||
isActivityPubUrlValid(remoteActor.url) &&
|
||||
isActorPublicKeyObjectValid(remoteActor.publicKey) &&
|
||||
isActorEndpointsObjectValid(remoteActor.endpoints)
|
||||
isActorEndpointsObjectValid(remoteActor.endpoints) &&
|
||||
(!remoteActor.summary || isVideoChannelDescriptionValid(remoteActor.summary)) &&
|
||||
setValidAttributedTo(remoteActor) &&
|
||||
// If this is not an account, it should be attributed to an account
|
||||
// In PeerTube we use this to attach a video channel to a specific account
|
||||
(remoteActor.type === 'Person' || remoteActor.attributedTo.length !== 0)
|
||||
}
|
||||
|
||||
function isActorFollowingCountValid (value: string) {
|
||||
|
@ -73,6 +88,40 @@ function isActorAcceptActivityValid (activity: any) {
|
|||
return isBaseActivityValid(activity, 'Accept')
|
||||
}
|
||||
|
||||
function isActorIdExist (id: number | string, res: Response) {
|
||||
let promise: Bluebird<ActorModel>
|
||||
|
||||
if (validator.isInt('' + id)) {
|
||||
promise = ActorModel.load(+id)
|
||||
} else { // UUID
|
||||
promise = ActorModel.loadByUUID('' + id)
|
||||
}
|
||||
|
||||
return isActorExist(promise, res)
|
||||
}
|
||||
|
||||
function isLocalActorNameExist (name: string, res: Response) {
|
||||
const promise = ActorModel.loadLocalByName(name)
|
||||
|
||||
return isActorExist(promise, res)
|
||||
}
|
||||
|
||||
async function isActorExist (p: Bluebird<ActorModel>, res: Response) {
|
||||
const actor = await p
|
||||
|
||||
if (!actor) {
|
||||
res.status(404)
|
||||
.send({ error: 'Actor not found' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.actor = actor
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -87,5 +136,9 @@ export {
|
|||
isActorFollowersCountValid,
|
||||
isActorFollowActivityValid,
|
||||
isActorAcceptActivityValid,
|
||||
isActorDeleteActivityValid
|
||||
isActorDeleteActivityValid,
|
||||
isActorIdExist,
|
||||
isLocalActorNameExist,
|
||||
isActorNameValid,
|
||||
isActorExist
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { isBaseActivityValid } from './misc'
|
||||
import { isVideoTorrentAddActivityValid } from './videos'
|
||||
import { isVideoChannelCreateActivityValid } from './video-channels'
|
||||
import { isVideoTorrentCreateActivityValid } from './videos'
|
||||
|
||||
function isAnnounceActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Announce') &&
|
||||
(
|
||||
isVideoChannelCreateActivityValid(activity.object) ||
|
||||
isVideoTorrentAddActivityValid(activity.object)
|
||||
isVideoTorrentCreateActivityValid(activity.object)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ function isActivityPubUrlValid (url: string) {
|
|||
isURLOptions.require_tld = false
|
||||
}
|
||||
|
||||
return exists(url) && validator.isURL(url, isURLOptions) && validator.isLength(url, CONSTRAINTS_FIELDS.ACCOUNTS.URL)
|
||||
return exists(url) && validator.isURL(url, isURLOptions) && validator.isLength(url, CONSTRAINTS_FIELDS.ACTOR.URL)
|
||||
}
|
||||
|
||||
function isBaseActivityValid (activity: any, type: string) {
|
||||
|
@ -35,7 +35,23 @@ function isBaseActivityValid (activity: any, type: string) {
|
|||
)
|
||||
}
|
||||
|
||||
function setValidAttributedTo (obj: any) {
|
||||
if (Array.isArray(obj.attributedTo) === false) {
|
||||
obj.attributedTo = []
|
||||
return true
|
||||
}
|
||||
|
||||
const newAttributesTo = obj.attributedTo.filter(a => {
|
||||
return (a.type === 'Group' || a.type === 'Person') && isActivityPubUrlValid(a.id)
|
||||
})
|
||||
|
||||
obj.attributedTo = newAttributesTo
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export {
|
||||
isActivityPubUrlValid,
|
||||
isBaseActivityValid
|
||||
isBaseActivityValid,
|
||||
setValidAttributedTo
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { isAccountFollowActivityValid } from './actor'
|
||||
import { isActorFollowActivityValid } from './actor'
|
||||
import { isBaseActivityValid } from './misc'
|
||||
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||
|
||||
function isUndoActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Undo') &&
|
||||
(
|
||||
isAccountFollowActivityValid(activity.object) ||
|
||||
isActorFollowActivityValid(activity.object) ||
|
||||
isLikeActivityValid(activity.object) ||
|
||||
isDislikeActivityValid(activity.object)
|
||||
)
|
||||
|
|
|
@ -2,11 +2,6 @@ import { isDateValid, isUUIDValid } from '../misc'
|
|||
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
|
||||
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
|
||||
|
||||
function isVideoChannelCreateActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Create') &&
|
||||
isVideoChannelObjectValid(activity.object)
|
||||
}
|
||||
|
||||
function isVideoChannelUpdateActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Update') &&
|
||||
isVideoChannelObjectValid(activity.object)
|
||||
|
@ -29,7 +24,6 @@ function isVideoChannelObjectValid (videoChannel: any) {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isVideoChannelCreateActivityValid,
|
||||
isVideoChannelUpdateActivityValid,
|
||||
isVideoChannelDeleteActivityValid,
|
||||
isVideoChannelObjectValid
|
||||
|
|
|
@ -10,10 +10,10 @@ import {
|
|||
isVideoTruncatedDescriptionValid,
|
||||
isVideoViewsValid
|
||||
} from '../videos'
|
||||
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
|
||||
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
|
||||
|
||||
function isVideoTorrentAddActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Add') &&
|
||||
function isVideoTorrentCreateActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Create') &&
|
||||
isVideoTorrentObjectValid(activity.object)
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,8 @@ function isActivityPubVideoDurationValid (value: string) {
|
|||
}
|
||||
|
||||
function isVideoTorrentObjectValid (video: any) {
|
||||
console.log(video)
|
||||
|
||||
return video.type === 'Video' &&
|
||||
isActivityPubUrlValid(video.id) &&
|
||||
isVideoNameValid(video.name) &&
|
||||
|
@ -59,13 +61,15 @@ function isVideoTorrentObjectValid (video: any) {
|
|||
(!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
|
||||
isRemoteVideoIconValid(video.icon) &&
|
||||
setValidRemoteVideoUrls(video) &&
|
||||
video.url.length !== 0
|
||||
video.url.length !== 0 &&
|
||||
setValidAttributedTo(video) &&
|
||||
video.attributedTo.length !== 0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isVideoTorrentAddActivityValid,
|
||||
isVideoTorrentCreateActivityValid,
|
||||
isVideoTorrentUpdateActivityValid,
|
||||
isVideoTorrentDeleteActivityValid,
|
||||
isVideoFlagValid
|
||||
|
|
|
@ -5,11 +5,11 @@ function isWebfingerResourceValid (value: string) {
|
|||
if (!exists(value)) return false
|
||||
if (value.startsWith('acct:') === false) return false
|
||||
|
||||
const accountWithHost = value.substr(5)
|
||||
const accountParts = accountWithHost.split('@')
|
||||
if (accountParts.length !== 2) return false
|
||||
const actorWithHost = value.substr(5)
|
||||
const actorParts = actorWithHost.split('@')
|
||||
if (actorParts.length !== 2) return false
|
||||
|
||||
const host = accountParts[1]
|
||||
const host = actorParts[1]
|
||||
|
||||
return host === CONFIG.WEBSERVER.HOST
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils'
|
||||
import { jsig } from './custom-jsonld-signature'
|
||||
import { logger } from './logger'
|
||||
|
@ -13,18 +13,18 @@ async function createPrivateAndPublicKeys () {
|
|||
return { privateKey: key, publicKey }
|
||||
}
|
||||
|
||||
function isSignatureVerified (fromAccount: AccountModel, signedDocument: object) {
|
||||
function isSignatureVerified (fromActor: ActorModel, signedDocument: object) {
|
||||
const publicKeyObject = {
|
||||
'@context': jsig.SECURITY_CONTEXT_URL,
|
||||
'@id': fromAccount.url,
|
||||
'@id': fromActor.url,
|
||||
'@type': 'CryptographicKey',
|
||||
owner: fromAccount.url,
|
||||
publicKeyPem: fromAccount.publicKey
|
||||
owner: fromActor.url,
|
||||
publicKeyPem: fromActor.publicKey
|
||||
}
|
||||
|
||||
const publicKeyOwnerObject = {
|
||||
'@context': jsig.SECURITY_CONTEXT_URL,
|
||||
'@id': fromAccount.url,
|
||||
'@id': fromActor.url,
|
||||
publicKey: [ publicKeyObject ]
|
||||
}
|
||||
|
||||
|
@ -40,10 +40,10 @@ function isSignatureVerified (fromAccount: AccountModel, signedDocument: object)
|
|||
})
|
||||
}
|
||||
|
||||
function signObject (byAccount: AccountModel, data: any) {
|
||||
function signObject (byActor: ActorModel, data: any) {
|
||||
const options = {
|
||||
privateKeyPem: byAccount.privateKey,
|
||||
creator: byAccount.url
|
||||
privateKeyPem: byActor.privateKey,
|
||||
creator: byActor.url
|
||||
}
|
||||
|
||||
return jsig.promises.sign(data, options)
|
||||
|
|
|
@ -3,8 +3,9 @@ import { Model } from 'sequelize-typescript'
|
|||
import { ResultList } from '../../shared'
|
||||
import { VideoResolution } from '../../shared/models/videos'
|
||||
import { CONFIG } from '../initializers'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { ApplicationModel } from '../models/application/application'
|
||||
import { pseudoRandomBytesPromise } from './core-utils'
|
||||
import { logger } from './logger'
|
||||
|
||||
|
@ -80,18 +81,19 @@ function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
|
|||
})
|
||||
}
|
||||
|
||||
let serverAccount: AccountModel
|
||||
async function getServerAccount () {
|
||||
if (serverAccount === undefined) {
|
||||
serverAccount = await AccountModel.loadApplication()
|
||||
let serverActor: ActorModel
|
||||
async function getServerActor () {
|
||||
if (serverActor === undefined) {
|
||||
const application = await ApplicationModel.load()
|
||||
serverActor = application.Account.Actor
|
||||
}
|
||||
|
||||
if (!serverAccount) {
|
||||
logger.error('Cannot load server account.')
|
||||
if (!serverActor) {
|
||||
logger.error('Cannot load server actor.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
return Promise.resolve(serverAccount)
|
||||
return Promise.resolve(serverActor)
|
||||
}
|
||||
|
||||
type SortType = { sortModel: any, sortValue: string }
|
||||
|
@ -105,6 +107,6 @@ export {
|
|||
isSignupAllowed,
|
||||
computeResolutionsToTranscode,
|
||||
resetSequelizeInstance,
|
||||
getServerAccount,
|
||||
getServerActor,
|
||||
SortType
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as WebFinger from 'webfinger.js'
|
||||
import { WebFingerData } from '../../shared'
|
||||
import { fetchRemoteAccount } from '../lib/activitypub'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { isTestInstance } from './core-utils'
|
||||
import { isActivityPubUrlValid } from './custom-validators/activitypub'
|
||||
|
||||
|
@ -11,9 +11,23 @@ const webfinger = new WebFinger({
|
|||
request_timeout: 3000
|
||||
})
|
||||
|
||||
async function getAccountFromWebfinger (nameWithHost: string) {
|
||||
const webfingerData: WebFingerData = await webfingerLookup(nameWithHost)
|
||||
async function loadActorUrlOrGetFromWebfinger (name: string, host: string) {
|
||||
const actor = await ActorModel.loadByNameAndHost(name, host)
|
||||
if (actor) return actor.url
|
||||
|
||||
const webfingerData: WebFingerData = await webfingerLookup(name + '@' + host)
|
||||
return getLinkOrThrow(webfingerData)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
loadActorUrlOrGetFromWebfinger
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getLinkOrThrow (webfingerData: WebFingerData) {
|
||||
if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.')
|
||||
|
||||
const selfLink = webfingerData.links.find(l => l.rel === 'self')
|
||||
|
@ -21,20 +35,9 @@ async function getAccountFromWebfinger (nameWithHost: string) {
|
|||
throw new Error('Cannot find self link or href is not a valid URL.')
|
||||
}
|
||||
|
||||
const account = await fetchRemoteAccount(selfLink.href)
|
||||
if (account === undefined) throw new Error('Cannot fetch remote account ' + selfLink.href)
|
||||
|
||||
return account
|
||||
return selfLink.href
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
getAccountFromWebfinger
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function webfingerLookup (nameWithHost: string) {
|
||||
return new Promise<WebFingerData>((res, rej) => {
|
||||
webfinger.lookup(nameWithHost, (err, p) => {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as config from 'config'
|
||||
import { join } from 'path'
|
||||
import { JobCategory, JobState, VideoRateType } from '../../shared/models'
|
||||
import { FollowState } from '../../shared/models/accounts'
|
||||
import { FollowState } from '../../shared/models/actors'
|
||||
import { ActivityPubActorType } from '../../shared/models/activitypub'
|
||||
import { VideoPrivacy } from '../../shared/models/videos'
|
||||
// Do not use barrels, remain constants as independent as possible
|
||||
import { isTestInstance, root } from '../helpers/core-utils'
|
||||
|
@ -210,7 +211,7 @@ const VIDEO_MIMETYPE_EXT = {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SERVER_ACCOUNT_NAME = 'peertube'
|
||||
const SERVER_ACTOR_NAME = 'peertube'
|
||||
|
||||
const ACTIVITY_PUB = {
|
||||
POTENTIAL_ACCEPT_HEADERS: [
|
||||
|
@ -229,6 +230,12 @@ const ACTIVITY_PUB = {
|
|||
}
|
||||
}
|
||||
|
||||
const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
|
||||
GROUP: 'Group',
|
||||
PERSON: 'Person',
|
||||
APPLICATION: 'Application'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Number of points we add/remove from a friend after a successful/bad request
|
||||
|
@ -350,12 +357,13 @@ export {
|
|||
REMOTE_SCHEME,
|
||||
FOLLOW_STATES,
|
||||
AVATARS_DIR,
|
||||
SERVER_ACCOUNT_NAME,
|
||||
SERVER_ACTOR_NAME,
|
||||
PRIVATE_RSA_KEY_SIZE,
|
||||
SORTABLE_COLUMNS,
|
||||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS,
|
||||
ACTIVITY_PUB,
|
||||
ACTIVITY_PUB_ACTOR_TYPES,
|
||||
THUMBNAILS_SIZE,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LANGUAGES,
|
||||
|
|
|
@ -3,9 +3,10 @@ import { isTestInstance } from '../helpers/core-utils'
|
|||
import { logger } from '../helpers/logger'
|
||||
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { AccountFollowModel } from '../models/account/account-follow'
|
||||
import { AccountVideoRateModel } from '../models/account/account-video-rate'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../models/activitypub/actor-follow'
|
||||
import { ApplicationModel } from '../models/application/application'
|
||||
import { AvatarModel } from '../models/avatar/avatar'
|
||||
import { JobModel } from '../models/job/job'
|
||||
|
@ -17,7 +18,6 @@ import { VideoModel } from '../models/video/video'
|
|||
import { VideoAbuseModel } from '../models/video/video-abuse'
|
||||
import { VideoBlacklistModel } from '../models/video/video-blacklist'
|
||||
import { VideoChannelModel } from '../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../models/video/video-channel-share'
|
||||
import { VideoFileModel } from '../models/video/video-file'
|
||||
import { VideoShareModel } from '../models/video/video-share'
|
||||
import { VideoTagModel } from '../models/video/video-tag'
|
||||
|
@ -56,6 +56,8 @@ const sequelizeTypescript = new SequelizeTypescript({
|
|||
async function initDatabaseModels (silent: boolean) {
|
||||
sequelizeTypescript.addModels([
|
||||
ApplicationModel,
|
||||
ActorModel,
|
||||
ActorFollowModel,
|
||||
AvatarModel,
|
||||
AccountModel,
|
||||
JobModel,
|
||||
|
@ -64,11 +66,9 @@ async function initDatabaseModels (silent: boolean) {
|
|||
ServerModel,
|
||||
TagModel,
|
||||
AccountVideoRateModel,
|
||||
AccountFollowModel,
|
||||
UserModel,
|
||||
VideoAbuseModel,
|
||||
VideoChannelModel,
|
||||
VideoChannelShareModel,
|
||||
VideoShareModel,
|
||||
VideoFileModel,
|
||||
VideoBlacklistModel,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as passwordGenerator from 'password-generator'
|
||||
import { UserRole } from '../../shared'
|
||||
import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||
import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib'
|
||||
import { logger, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||
import { createApplicationActor, createUserAccountAndChannel } from '../lib/user'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { ApplicationModel } from '../models/application/application'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client'
|
||||
import { applicationExist, clientsExist, usersExist } from './checker'
|
||||
import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants'
|
||||
import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants'
|
||||
import { sequelizeTypescript } from './database'
|
||||
|
||||
async function installApplication () {
|
||||
|
@ -134,15 +134,12 @@ async function createApplicationIfNotExist () {
|
|||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating Application table.')
|
||||
const applicationInstance = await ApplicationModel.create({ migrationVersion: LAST_MIGRATION_VERSION })
|
||||
|
||||
logger.info('Creating application account.')
|
||||
|
||||
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined)
|
||||
const application = await ApplicationModel.create({
|
||||
migrationVersion: LAST_MIGRATION_VERSION
|
||||
})
|
||||
|
||||
const { publicKey, privateKey } = await createPrivateAndPublicKeys()
|
||||
accountCreated.set('publicKey', publicKey)
|
||||
accountCreated.set('privateKey', privateKey)
|
||||
|
||||
return accountCreated.save()
|
||||
return createApplicationActor(application.id)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { shareVideoByServer } from '../../lib/activitypub/share'
|
|||
import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
|
||||
import { createLocalAccountWithoutKeys } from '../../lib/user'
|
||||
import { ApplicationModel } from '../../models/application/application'
|
||||
import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants'
|
||||
import { JOB_CATEGORIES, SERVER_ACTOR_NAME } from '../constants'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
|
@ -66,7 +66,7 @@ async function up (utils: {
|
|||
// Create application account
|
||||
{
|
||||
const applicationInstance = await ApplicationModel.findOne()
|
||||
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined)
|
||||
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined)
|
||||
|
||||
const { publicKey, privateKey } = await createPrivateAndPublicKeys()
|
||||
accountCreated.set('publicKey', publicKey)
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { Transaction } from 'sequelize'
|
||||
import * as url from 'url'
|
||||
import { ActivityPubActor } from '../../../shared/models/activitypub'
|
||||
import { doRequest, logger, retryTransactionWrapper } from '../../helpers'
|
||||
import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub'
|
||||
import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { ServerModel } from '../../models/server/server'
|
||||
|
||||
async function getOrCreateAccountAndServer (accountUrl: string) {
|
||||
let account = await AccountModel.loadByUrl(accountUrl)
|
||||
|
||||
// We don't have this account in our database, fetch it on remote
|
||||
if (!account) {
|
||||
account = await fetchRemoteAccount(accountUrl)
|
||||
if (account === undefined) throw new Error('Cannot fetch remote account.')
|
||||
|
||||
const options = {
|
||||
arguments: [ account ],
|
||||
errorMessage: 'Cannot save account and server with many retries.'
|
||||
}
|
||||
account = await retryTransactionWrapper(saveAccountAndServerIfNotExist, options)
|
||||
}
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
function saveAccountAndServerIfNotExist (account: AccountModel, t?: Transaction): Bluebird<AccountModel> | Promise<AccountModel> {
|
||||
if (t !== undefined) {
|
||||
return save(t)
|
||||
} else {
|
||||
return sequelizeTypescript.transaction(t => {
|
||||
return save(t)
|
||||
})
|
||||
}
|
||||
|
||||
async function save (t: Transaction) {
|
||||
const accountHost = url.parse(account.url).host
|
||||
|
||||
const serverOptions = {
|
||||
where: {
|
||||
host: accountHost
|
||||
},
|
||||
defaults: {
|
||||
host: accountHost
|
||||
},
|
||||
transaction: t
|
||||
}
|
||||
const [ server ] = await ServerModel.findOrCreate(serverOptions)
|
||||
|
||||
// Save our new account in database
|
||||
account.set('serverId', server.id)
|
||||
account = await account.save({ transaction: t })
|
||||
|
||||
return account
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRemoteAccount (accountUrl: string) {
|
||||
const options = {
|
||||
uri: accountUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': ACTIVITY_PUB.ACCEPT_HEADER
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Fetching remote account %s.', accountUrl)
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote account %s.', accountUrl, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const accountJSON: ActivityPubActor = JSON.parse(requestResult.body)
|
||||
if (isRemoteAccountValid(accountJSON) === false) {
|
||||
logger.debug('Remote account JSON is not valid.', { accountJSON })
|
||||
return undefined
|
||||
}
|
||||
|
||||
const followersCount = await fetchAccountCount(accountJSON.followers)
|
||||
const followingCount = await fetchAccountCount(accountJSON.following)
|
||||
|
||||
return new AccountModel({
|
||||
uuid: accountJSON.uuid,
|
||||
name: accountJSON.preferredUsername,
|
||||
url: accountJSON.url,
|
||||
publicKey: accountJSON.publicKey.publicKeyPem,
|
||||
privateKey: null,
|
||||
followersCount: followersCount,
|
||||
followingCount: followingCount,
|
||||
inboxUrl: accountJSON.inbox,
|
||||
outboxUrl: accountJSON.outbox,
|
||||
sharedInboxUrl: accountJSON.endpoints.sharedInbox,
|
||||
followersUrl: accountJSON.followers,
|
||||
followingUrl: accountJSON.following
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
getOrCreateAccountAndServer,
|
||||
fetchRemoteAccount,
|
||||
saveAccountAndServerIfNotExist
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function fetchAccountCount (url: string) {
|
||||
const options = {
|
||||
uri: url,
|
||||
method: 'GET'
|
||||
}
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote account count %s.', url, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return requestResult.totalItems ? requestResult.totalItems : 0
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { Transaction } from 'sequelize'
|
||||
import * as url from 'url'
|
||||
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
|
||||
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
|
||||
import { createPrivateAndPublicKeys, doRequest, logger, retryTransactionWrapper } from '../../helpers'
|
||||
import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub'
|
||||
import { ACTIVITY_PUB, CONFIG, sequelizeTypescript } from '../../initializers'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { ServerModel } from '../../models/server/server'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
|
||||
// Set account keys, this could be long so process after the account creation and do not block the client
|
||||
function setAsyncActorKeys (actor: ActorModel) {
|
||||
return createPrivateAndPublicKeys()
|
||||
.then(({ publicKey, privateKey }) => {
|
||||
actor.set('publicKey', publicKey)
|
||||
actor.set('privateKey', privateKey)
|
||||
return actor.save()
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err)
|
||||
return actor
|
||||
})
|
||||
}
|
||||
|
||||
async function getOrCreateActorAndServerAndModel (actorUrl: string, recurseIfNeeded = true) {
|
||||
let actor = await ActorModel.loadByUrl(actorUrl)
|
||||
|
||||
// We don't have this actor in our database, fetch it on remote
|
||||
if (!actor) {
|
||||
const result = await fetchRemoteActor(actorUrl)
|
||||
if (result === undefined) throw new Error('Cannot fetch remote actor.')
|
||||
|
||||
// Create the attributed to actor
|
||||
// In PeerTube a video channel is owned by an account
|
||||
let ownerActor: ActorModel = undefined
|
||||
if (recurseIfNeeded === true && result.actor.type === 'Group') {
|
||||
const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
|
||||
if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
|
||||
|
||||
try {
|
||||
// Assert we don't recurse another time
|
||||
ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, false)
|
||||
} catch (err) {
|
||||
logger.error('Cannot get or create account attributed to video channel ' + actor.url)
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const options = {
|
||||
arguments: [ result, ownerActor ],
|
||||
errorMessage: 'Cannot save actor and server with many retries.'
|
||||
}
|
||||
actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
|
||||
}
|
||||
|
||||
return actor
|
||||
}
|
||||
|
||||
function saveActorAndServerAndModelIfNotExist (
|
||||
result: FetchRemoteActorResult,
|
||||
ownerActor?: ActorModel,
|
||||
t?: Transaction
|
||||
): Bluebird<ActorModel> | Promise<ActorModel> {
|
||||
let actor = result.actor
|
||||
|
||||
if (t !== undefined) return save(t)
|
||||
|
||||
return sequelizeTypescript.transaction(t => save(t))
|
||||
|
||||
async function save (t: Transaction) {
|
||||
const actorHost = url.parse(actor.url).host
|
||||
|
||||
const serverOptions = {
|
||||
where: {
|
||||
host: actorHost
|
||||
},
|
||||
defaults: {
|
||||
host: actorHost
|
||||
},
|
||||
transaction: t
|
||||
}
|
||||
const [ server ] = await ServerModel.findOrCreate(serverOptions)
|
||||
|
||||
// Save our new account in database
|
||||
actor.set('serverId', server.id)
|
||||
|
||||
// Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
|
||||
// (which could be false in a retried query)
|
||||
const actorCreated = await ActorModel.create(actor.toJSON(), { transaction: t })
|
||||
|
||||
if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
|
||||
const account = await saveAccount(actorCreated, result, t)
|
||||
actorCreated.Account = account
|
||||
actorCreated.Account.Actor = actorCreated
|
||||
} else if (actorCreated.type === 'Group') { // Video channel
|
||||
const videoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
|
||||
actorCreated.VideoChannel = videoChannel
|
||||
actorCreated.VideoChannel.Actor = actorCreated
|
||||
}
|
||||
|
||||
return actorCreated
|
||||
}
|
||||
}
|
||||
|
||||
type FetchRemoteActorResult = {
|
||||
actor: ActorModel
|
||||
preferredUsername: string
|
||||
summary: string
|
||||
attributedTo: ActivityPubAttributedTo[]
|
||||
}
|
||||
async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResult> {
|
||||
const options = {
|
||||
uri: actorUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': ACTIVITY_PUB.ACCEPT_HEADER
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Fetching remote actor %s.', actorUrl)
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote actor %s.', actorUrl, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const actorJSON: ActivityPubActor = JSON.parse(requestResult.body)
|
||||
if (isRemoteActorValid(actorJSON) === false) {
|
||||
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
|
||||
return undefined
|
||||
}
|
||||
|
||||
const followersCount = await fetchActorTotalItems(actorJSON.followers)
|
||||
const followingCount = await fetchActorTotalItems(actorJSON.following)
|
||||
|
||||
const actor = new ActorModel({
|
||||
type: actorJSON.type,
|
||||
uuid: actorJSON.uuid,
|
||||
name: actorJSON.name,
|
||||
url: actorJSON.url,
|
||||
publicKey: actorJSON.publicKey.publicKeyPem,
|
||||
privateKey: null,
|
||||
followersCount: followersCount,
|
||||
followingCount: followingCount,
|
||||
inboxUrl: actorJSON.inbox,
|
||||
outboxUrl: actorJSON.outbox,
|
||||
sharedInboxUrl: actorJSON.endpoints.sharedInbox,
|
||||
followersUrl: actorJSON.followers,
|
||||
followingUrl: actorJSON.following
|
||||
})
|
||||
|
||||
return {
|
||||
actor,
|
||||
preferredUsername: actorJSON.preferredUsername,
|
||||
summary: actorJSON.summary,
|
||||
attributedTo: actorJSON.attributedTo
|
||||
}
|
||||
}
|
||||
|
||||
function buildActorInstance (type: ActivityPubActorType, url: string, name: string, uuid?: string) {
|
||||
return new ActorModel({
|
||||
type,
|
||||
url,
|
||||
name,
|
||||
uuid,
|
||||
publicKey: null,
|
||||
privateKey: null,
|
||||
followersCount: 0,
|
||||
followingCount: 0,
|
||||
inboxUrl: url + '/inbox',
|
||||
outboxUrl: url + '/outbox',
|
||||
sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
|
||||
followersUrl: url + '/followers',
|
||||
followingUrl: url + '/following'
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
getOrCreateActorAndServerAndModel,
|
||||
saveActorAndServerAndModelIfNotExist,
|
||||
fetchRemoteActor,
|
||||
buildActorInstance,
|
||||
setAsyncActorKeys
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function fetchActorTotalItems (url: string) {
|
||||
const options = {
|
||||
uri: url,
|
||||
method: 'GET'
|
||||
}
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote actor count %s.', url, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return requestResult.totalItems ? requestResult.totalItems : 0
|
||||
}
|
||||
|
||||
function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
|
||||
const account = new AccountModel({
|
||||
name: result.preferredUsername,
|
||||
actorId: actor.id
|
||||
})
|
||||
|
||||
return account.save({ transaction: t })
|
||||
}
|
||||
|
||||
async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
|
||||
const videoChannel = new VideoChannelModel({
|
||||
name: result.preferredUsername,
|
||||
description: result.summary,
|
||||
actorId: actor.id,
|
||||
accountId: ownerActor.Account.id
|
||||
})
|
||||
|
||||
return videoChannel.save({ transaction: t })
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler'
|
||||
|
||||
async function addFetchOutboxJob (account: AccountModel, t: Transaction) {
|
||||
async function addFetchOutboxJob (actor: ActorModel, t: Transaction) {
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris: [ account.outboxUrl ]
|
||||
uris: [ actor.outboxUrl ]
|
||||
}
|
||||
|
||||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpFetcherHandler', jobPayload)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
export * from './process'
|
||||
export * from './send'
|
||||
export * from './account'
|
||||
export * from './actor'
|
||||
export * from './fetch'
|
||||
export * from './share'
|
||||
export * from './video-channels'
|
||||
export * from './videos'
|
||||
export * from './url'
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export * from './process'
|
||||
export * from './process-accept'
|
||||
export * from './process-add'
|
||||
export * from './process-announce'
|
||||
export * from './process-create'
|
||||
export * from './process-delete'
|
||||
|
|
|
@ -1,29 +1,13 @@
|
|||
import * as magnetUtil from 'magnet-uri'
|
||||
import { VideoTorrentObject } from '../../../../shared'
|
||||
import { VideoChannelObject } from '../../../../shared/models/activitypub/objects'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { doRequest } from '../../../helpers'
|
||||
import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
|
||||
import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
|
||||
function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountModel) {
|
||||
return {
|
||||
name: videoChannelObject.name,
|
||||
description: videoChannelObject.content,
|
||||
uuid: videoChannelObject.uuid,
|
||||
url: videoChannelObject.id,
|
||||
createdAt: new Date(videoChannelObject.published),
|
||||
updatedAt: new Date(videoChannelObject.updated),
|
||||
remote: true,
|
||||
accountId: account.id
|
||||
}
|
||||
}
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
|
||||
async function videoActivityObjectToDBAttributes (
|
||||
videoChannel: VideoChannelModel,
|
||||
|
@ -120,13 +104,13 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
|
|||
uri: share,
|
||||
json: true
|
||||
})
|
||||
const actor = json['actor']
|
||||
if (!actor) continue
|
||||
const actorUrl = json['actor']
|
||||
if (!actorUrl) continue
|
||||
|
||||
const account = await getOrCreateAccountAndServer(actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
|
||||
const entry = {
|
||||
accountId: account.id,
|
||||
actorId: actor.id,
|
||||
videoId: instance.id
|
||||
}
|
||||
|
||||
|
@ -137,36 +121,10 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
|
|||
}
|
||||
}
|
||||
|
||||
async function addVideoChannelShares (instance: VideoChannelModel, shares: string[]) {
|
||||
for (const share of shares) {
|
||||
// Fetch url
|
||||
const json = await doRequest({
|
||||
uri: share,
|
||||
json: true
|
||||
})
|
||||
const actor = json['actor']
|
||||
if (!actor) continue
|
||||
|
||||
const account = await getOrCreateAccountAndServer(actor)
|
||||
|
||||
const entry = {
|
||||
accountId: account.id,
|
||||
videoChannelId: instance.id
|
||||
}
|
||||
|
||||
await VideoChannelShareModel.findOrCreate({
|
||||
where: entry,
|
||||
defaults: entry
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
videoFileActivityUrlToDBAttributes,
|
||||
videoActivityObjectToDBAttributes,
|
||||
videoChannelActivityObjectToDBAttributes,
|
||||
addVideoChannelShares,
|
||||
addVideoShares
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { ActivityAccept } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { addFetchOutboxJob } from '../fetch'
|
||||
|
||||
async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) {
|
||||
if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
|
||||
async function processAcceptActivity (activity: ActivityAccept, inboxActor?: ActorModel) {
|
||||
if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
|
||||
|
||||
const targetAccount = await AccountModel.loadByUrl(activity.actor)
|
||||
const targetActor = await ActorModel.loadByUrl(activity.actor)
|
||||
|
||||
return processAccept(inboxAccount, targetAccount)
|
||||
return processAccept(inboxActor, targetActor)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -19,11 +19,11 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processAccept (account: AccountModel, targetAccount: AccountModel) {
|
||||
const follow = await AccountFollowModel.loadByAccountAndTarget(account.id, targetAccount.id)
|
||||
async function processAccept (actor: ActorModel, targetActor: ActorModel) {
|
||||
const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
|
||||
if (!follow) throw new Error('Cannot find associated follow.')
|
||||
|
||||
follow.set('state', 'accepted')
|
||||
await follow.save()
|
||||
await addFetchOutboxJob(targetAccount, undefined)
|
||||
await addFetchOutboxJob(targetActor, undefined)
|
||||
}
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { VideoTorrentObject } from '../../../../shared'
|
||||
import { ActivityAdd } from '../../../../shared/models/activitypub'
|
||||
import { VideoRateType } from '../../../../shared/models/videos'
|
||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||
import { TagModel } from '../../../models/video/tag'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoFileModel } from '../../../models/video/video-file'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { getOrCreateVideoChannel } from '../video-channels'
|
||||
import { generateThumbnailFromUrl } from '../videos'
|
||||
import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||
|
||||
async function processAddActivity (activity: ActivityAdd) {
|
||||
const activityObject = activity.object
|
||||
const activityType = activityObject.type
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
|
||||
if (activityType === 'Video') {
|
||||
const videoChannelUrl = activity.target
|
||||
const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
|
||||
|
||||
return processAddVideo(account, activity, videoChannel, activityObject)
|
||||
}
|
||||
|
||||
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
processAddActivity
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processAddVideo (account: AccountModel,
|
||||
activity: ActivityAdd,
|
||||
videoChannel: VideoChannelModel,
|
||||
videoToCreateData: VideoTorrentObject) {
|
||||
const options = {
|
||||
arguments: [ account, activity, videoChannel, videoToCreateData ],
|
||||
errorMessage: 'Cannot insert the remote video with many retries.'
|
||||
}
|
||||
|
||||
const video = await retryTransactionWrapper(addRemoteVideo, options)
|
||||
|
||||
// Process outside the transaction because we could fetch remote data
|
||||
if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
|
||||
await createRates(videoToCreateData.likes.orderedItems, video, 'like')
|
||||
}
|
||||
|
||||
if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
|
||||
await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
|
||||
}
|
||||
|
||||
if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
|
||||
await addVideoShares(video, videoToCreateData.shares.orderedItems)
|
||||
}
|
||||
|
||||
return video
|
||||
}
|
||||
|
||||
function addRemoteVideo (account: AccountModel,
|
||||
activity: ActivityAdd,
|
||||
videoChannel: VideoChannelModel,
|
||||
videoToCreateData: VideoTorrentObject) {
|
||||
logger.debug('Adding remote video %s.', videoToCreateData.id)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const sequelizeOptions = {
|
||||
transaction: t
|
||||
}
|
||||
|
||||
if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
|
||||
|
||||
const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
|
||||
if (videoFromDatabase) return videoFromDatabase
|
||||
|
||||
const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
|
||||
const video = VideoModel.build(videoData)
|
||||
|
||||
// Don't block on request
|
||||
generateThumbnailFromUrl(video, videoToCreateData.icon)
|
||||
.catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
|
||||
|
||||
const videoCreated = await video.save(sequelizeOptions)
|
||||
|
||||
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
|
||||
if (videoFileAttributes.length === 0) {
|
||||
throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
|
||||
}
|
||||
|
||||
const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
|
||||
await Promise.all(tasks)
|
||||
|
||||
const tags = videoToCreateData.tag.map(t => t.name)
|
||||
const tagInstances = await TagModel.findOrCreateTags(tags, t)
|
||||
await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
|
||||
|
||||
logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
|
||||
|
||||
return videoCreated
|
||||
})
|
||||
}
|
||||
|
||||
async function createRates (accountUrls: string[], video: VideoModel, rate: VideoRateType) {
|
||||
let rateCounts = 0
|
||||
const tasks: Bluebird<any>[] = []
|
||||
|
||||
for (const accountUrl of accountUrls) {
|
||||
const account = await getOrCreateAccountAndServer(accountUrl)
|
||||
const p = AccountVideoRateModel
|
||||
.create({
|
||||
videoId: video.id,
|
||||
accountId: account.id,
|
||||
type: rate
|
||||
})
|
||||
.then(() => rateCounts += 1)
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
await Promise.all(tasks)
|
||||
|
||||
logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
|
||||
|
||||
// This is "likes" and "dislikes"
|
||||
await video.increment(rate + 's', { by: rateCounts })
|
||||
|
||||
return
|
||||
}
|
|
@ -1,24 +1,19 @@
|
|||
import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub'
|
||||
import { ActivityAnnounce } from '../../../../shared/models/activitypub'
|
||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
import { processAddActivity } from './process-add'
|
||||
import { processCreateActivity } from './process-create'
|
||||
|
||||
async function processAnnounceActivity (activity: ActivityAnnounce) {
|
||||
const announcedActivity = activity.object
|
||||
const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actorAnnouncer = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
|
||||
return processVideoChannelShare(accountAnnouncer, activity)
|
||||
} else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
|
||||
return processVideoShare(accountAnnouncer, activity)
|
||||
if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'Video') {
|
||||
return processVideoShare(actorAnnouncer, activity)
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
|
@ -37,60 +32,24 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
|
||||
function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
|
||||
const options = {
|
||||
arguments: [ accountAnnouncer, activity ],
|
||||
errorMessage: 'Cannot share the video channel with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(shareVideoChannel, options)
|
||||
}
|
||||
|
||||
async function shareVideoChannel (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
|
||||
const announcedActivity = activity.object as ActivityCreate
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
// Add share entry
|
||||
const videoChannel: VideoChannelModel = await processCreateActivity(announcedActivity)
|
||||
const share = {
|
||||
accountId: accountAnnouncer.id,
|
||||
videoChannelId: videoChannel.id
|
||||
}
|
||||
|
||||
const [ , created ] = await VideoChannelShareModel.findOrCreate({
|
||||
where: share,
|
||||
defaults: share,
|
||||
transaction: t
|
||||
})
|
||||
|
||||
if (videoChannel.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ accountAnnouncer ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
|
||||
function processVideoShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
|
||||
const options = {
|
||||
arguments: [ accountAnnouncer, activity ],
|
||||
arguments: [ actorAnnouncer, activity ],
|
||||
errorMessage: 'Cannot share the video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(shareVideo, options)
|
||||
}
|
||||
|
||||
function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
|
||||
const announcedActivity = activity.object as ActivityAdd
|
||||
function shareVideo (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
|
||||
const announcedActivity = activity.object
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
// Add share entry
|
||||
const video: VideoModel = await processAddActivity(announcedActivity)
|
||||
const video: VideoModel = await processCreateActivity(announcedActivity)
|
||||
|
||||
const share = {
|
||||
accountId: accountAnnouncer.id,
|
||||
actorId: actorAnnouncer.id,
|
||||
videoId: video.id
|
||||
}
|
||||
|
||||
|
@ -102,7 +61,7 @@ function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce)
|
|||
|
||||
if (video.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ accountAnnouncer ]
|
||||
const exceptions = [ actorAnnouncer ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
import { ActivityCreate, VideoChannelObject } from '../../../../shared'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
|
||||
import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
|
||||
import { VideoRateType } from '../../../../shared/models/videos'
|
||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { TagModel } from '../../../models/video/tag'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoAbuseModel } from '../../../models/video/video-abuse'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { VideoFileModel } from '../../../models/video/video-file'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
import { getVideoChannelActivityPubUrl } from '../url'
|
||||
import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc'
|
||||
import { generateThumbnailFromUrl } from '../videos'
|
||||
import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||
|
||||
async function processCreateActivity (activity: ActivityCreate) {
|
||||
const activityObject = activity.object
|
||||
const activityType = activityObject.type
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
if (activityType === 'View') {
|
||||
return processCreateView(account, activity)
|
||||
return processCreateView(actor, activity)
|
||||
} else if (activityType === 'Dislike') {
|
||||
return processCreateDislike(account, activity)
|
||||
} else if (activityType === 'VideoChannel') {
|
||||
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
|
||||
return processCreateDislike(actor, activity)
|
||||
} else if (activityType === 'Video') {
|
||||
return processCreateVideo(actor, activity)
|
||||
} else if (activityType === 'Flag') {
|
||||
return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
|
||||
return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
|
||||
}
|
||||
|
||||
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
|
||||
|
@ -39,17 +42,123 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) {
|
||||
async function processCreateVideo (
|
||||
actor: ActorModel,
|
||||
activity: ActivityCreate
|
||||
) {
|
||||
const videoToCreateData = activity.object as VideoTorrentObject
|
||||
|
||||
const channel = videoToCreateData.attributedTo.find(a => a.type === 'Group')
|
||||
if (!channel) throw new Error('Cannot find associated video channel to video ' + videoToCreateData.url)
|
||||
|
||||
const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
|
||||
|
||||
const options = {
|
||||
arguments: [ byAccount, activity ],
|
||||
arguments: [ actor, activity, videoToCreateData, channelActor ],
|
||||
errorMessage: 'Cannot insert the remote video with many retries.'
|
||||
}
|
||||
|
||||
const video = await retryTransactionWrapper(createRemoteVideo, options)
|
||||
|
||||
// Process outside the transaction because we could fetch remote data
|
||||
if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
|
||||
await createRates(videoToCreateData.likes.orderedItems, video, 'like')
|
||||
}
|
||||
|
||||
if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
|
||||
await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
|
||||
}
|
||||
|
||||
if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
|
||||
await addVideoShares(video, videoToCreateData.shares.orderedItems)
|
||||
}
|
||||
|
||||
return video
|
||||
}
|
||||
|
||||
function createRemoteVideo (
|
||||
account: ActorModel,
|
||||
activity: ActivityCreate,
|
||||
videoToCreateData: VideoTorrentObject,
|
||||
channelActor: ActorModel
|
||||
) {
|
||||
logger.debug('Adding remote video %s.', videoToCreateData.id)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const sequelizeOptions = {
|
||||
transaction: t
|
||||
}
|
||||
const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
|
||||
if (videoFromDatabase) return videoFromDatabase
|
||||
|
||||
const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoToCreateData, activity.to, activity.cc)
|
||||
const video = VideoModel.build(videoData)
|
||||
|
||||
// Don't block on request
|
||||
generateThumbnailFromUrl(video, videoToCreateData.icon)
|
||||
.catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
|
||||
|
||||
const videoCreated = await video.save(sequelizeOptions)
|
||||
|
||||
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
|
||||
if (videoFileAttributes.length === 0) {
|
||||
throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
|
||||
}
|
||||
|
||||
const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
|
||||
await Promise.all(tasks)
|
||||
|
||||
const tags = videoToCreateData.tag.map(t => t.name)
|
||||
const tagInstances = await TagModel.findOrCreateTags(tags, t)
|
||||
await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
|
||||
|
||||
logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
|
||||
|
||||
return videoCreated
|
||||
})
|
||||
}
|
||||
|
||||
async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
|
||||
let rateCounts = 0
|
||||
const tasks: Bluebird<any>[] = []
|
||||
|
||||
for (const actorUrl of actorUrls) {
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
const p = AccountVideoRateModel
|
||||
.create({
|
||||
videoId: video.id,
|
||||
accountId: actor.Account.id,
|
||||
type: rate
|
||||
})
|
||||
.then(() => rateCounts += 1)
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
await Promise.all(tasks)
|
||||
|
||||
logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
|
||||
|
||||
// This is "likes" and "dislikes"
|
||||
await video.increment(rate + 's', { by: rateCounts })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
|
||||
const options = {
|
||||
arguments: [ byActor, activity ],
|
||||
errorMessage: 'Cannot dislike the video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(createVideoDislike, options)
|
||||
}
|
||||
|
||||
function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) {
|
||||
function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
|
||||
const dislike = activity.object as DislikeObject
|
||||
const byAccount = byActor.Account
|
||||
|
||||
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
|
||||
|
@ -69,20 +178,20 @@ function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate)
|
|||
|
||||
if (video.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
const exceptions = [ byActor ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) {
|
||||
async function processCreateView (byAccount: ActorModel, activity: ActivityCreate) {
|
||||
const view = activity.object as ViewObject
|
||||
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
|
||||
|
||||
if (!video) throw new Error('Unknown video ' + view.object)
|
||||
|
||||
const account = await AccountModel.loadByUrl(view.actor)
|
||||
const account = await ActorModel.loadByUrl(view.actor)
|
||||
if (!account) throw new Error('Unknown account ' + view.actor)
|
||||
|
||||
await video.increment('views')
|
||||
|
@ -94,51 +203,21 @@ async function processCreateView (byAccount: AccountModel, activity: ActivityCre
|
|||
}
|
||||
}
|
||||
|
||||
async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
|
||||
function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannelToCreateData ],
|
||||
errorMessage: 'Cannot insert the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
const videoChannel = await retryTransactionWrapper(addRemoteVideoChannel, options)
|
||||
|
||||
if (videoChannelToCreateData.shares && Array.isArray(videoChannelToCreateData.shares.orderedItems)) {
|
||||
await addVideoChannelShares(videoChannel, videoChannelToCreateData.shares.orderedItems)
|
||||
}
|
||||
|
||||
return videoChannel
|
||||
}
|
||||
|
||||
function addRemoteVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
|
||||
logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
let videoChannel = await VideoChannelModel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
|
||||
if (videoChannel) return videoChannel
|
||||
|
||||
const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
|
||||
videoChannel = new VideoChannelModel(videoChannelData)
|
||||
videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
|
||||
|
||||
videoChannel = await videoChannel.save({ transaction: t })
|
||||
logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
|
||||
|
||||
return videoChannel
|
||||
})
|
||||
}
|
||||
|
||||
function processCreateVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
const options = {
|
||||
arguments: [ account, videoAbuseToCreateData ],
|
||||
arguments: [ actor, videoAbuseToCreateData ],
|
||||
errorMessage: 'Cannot insert the remote video abuse with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(addRemoteVideoAbuse, options)
|
||||
}
|
||||
|
||||
function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
|
||||
|
||||
const account = actor.Account
|
||||
if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
|
||||
if (!video) {
|
||||
|
|
|
@ -2,28 +2,30 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
|
|||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
|
||||
async function processDeleteActivity (activity: ActivityDelete) {
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
if (account.url === activity.id) {
|
||||
return processDeleteAccount(account)
|
||||
if (actor.url === activity.id) {
|
||||
if (actor.type === 'Person') {
|
||||
if (!actor.Account) throw new Error('Actor ' + actor.url + ' is a person but we cannot find it in database.')
|
||||
|
||||
return processDeleteAccount(actor.Account)
|
||||
} else if (actor.type === 'Group') {
|
||||
if (!actor.VideoChannel) throw new Error('Actor ' + actor.url + ' is a group but we cannot find it in database.')
|
||||
|
||||
return processDeleteVideoChannel(actor.VideoChannel)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
|
||||
if (videoObject !== undefined) {
|
||||
return processDeleteVideo(account, videoObject)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let videoChannelObject = await VideoChannelModel.loadByUrl(activity.id)
|
||||
if (videoChannelObject !== undefined) {
|
||||
return processDeleteVideoChannel(account, videoChannelObject)
|
||||
return processDeleteVideo(actor, videoObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,21 +40,21 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) {
|
||||
async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
|
||||
const options = {
|
||||
arguments: [ account, videoToDelete ],
|
||||
arguments: [ actor, videoToDelete ],
|
||||
errorMessage: 'Cannot remove the remote video with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteVideo, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) {
|
||||
async function deleteRemoteVideo (actor: ActorModel, videoToDelete: VideoModel) {
|
||||
logger.debug('Removing remote video "%s".', videoToDelete.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (videoToDelete.VideoChannel.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url)
|
||||
if (videoToDelete.VideoChannel.Account.Actor.id !== actor.id) {
|
||||
throw new Error('Account ' + actor.url + ' does not own video channel ' + videoToDelete.VideoChannel.Actor.url)
|
||||
}
|
||||
|
||||
await videoToDelete.destroy({ transaction: t })
|
||||
|
@ -61,29 +63,6 @@ async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoMod
|
|||
logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannelToRemove ],
|
||||
errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
|
||||
logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (videoChannelToRemove.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
|
||||
}
|
||||
|
||||
await videoChannelToRemove.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteAccount (accountToRemove: AccountModel) {
|
||||
const options = {
|
||||
arguments: [ accountToRemove ],
|
||||
|
@ -94,11 +73,30 @@ async function processDeleteAccount (accountToRemove: AccountModel) {
|
|||
}
|
||||
|
||||
async function deleteRemoteAccount (accountToRemove: AccountModel) {
|
||||
logger.debug('Removing remote account "%s".', accountToRemove.uuid)
|
||||
logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await accountToRemove.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote account with uuid %s removed.', accountToRemove.uuid)
|
||||
logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
|
||||
const options = {
|
||||
arguments: [ videoChannelToRemove ],
|
||||
errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel) {
|
||||
logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await videoChannelToRemove.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { ActivityFollow } from '../../../../shared/models/activitypub'
|
||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
import { sendAccept } from '../send'
|
||||
|
||||
async function processFollowActivity (activity: ActivityFollow) {
|
||||
const activityObject = activity.object
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
return processFollow(account, activityObject)
|
||||
return processFollow(actor, activityObject)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -21,46 +21,46 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processFollow (account: AccountModel, targetAccountURL: string) {
|
||||
function processFollow (actor: ActorModel, targetActorURL: string) {
|
||||
const options = {
|
||||
arguments: [ account, targetAccountURL ],
|
||||
arguments: [ actor, targetActorURL ],
|
||||
errorMessage: 'Cannot follow with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(follow, options)
|
||||
}
|
||||
|
||||
async function follow (account: AccountModel, targetAccountURL: string) {
|
||||
async function follow (actor: ActorModel, targetActorURL: string) {
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
const targetAccount = await AccountModel.loadByUrl(targetAccountURL, t)
|
||||
const targetActor = await ActorModel.loadByUrl(targetActorURL, t)
|
||||
|
||||
if (!targetAccount) throw new Error('Unknown account')
|
||||
if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
|
||||
if (!targetActor) throw new Error('Unknown actor')
|
||||
if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
|
||||
|
||||
const [ accountFollow ] = await AccountFollowModel.findOrCreate({
|
||||
const [ actorFollow ] = await ActorFollowModel.findOrCreate({
|
||||
where: {
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id
|
||||
actorId: actor.id,
|
||||
targetActorId: targetActor.id
|
||||
},
|
||||
defaults: {
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
actorId: actor.id,
|
||||
targetActorId: targetActor.id,
|
||||
state: 'accepted'
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
|
||||
if (accountFollow.state !== 'accepted') {
|
||||
accountFollow.state = 'accepted'
|
||||
await accountFollow.save({ transaction: t })
|
||||
if (actorFollow.state !== 'accepted') {
|
||||
actorFollow.state = 'accepted'
|
||||
await actorFollow.save({ transaction: t })
|
||||
}
|
||||
|
||||
accountFollow.AccountFollower = account
|
||||
accountFollow.AccountFollowing = targetAccount
|
||||
actorFollow.ActorFollower = actor
|
||||
actorFollow.ActorFollowing = targetActor
|
||||
|
||||
// Target sends to account he accepted the follow request
|
||||
return sendAccept(accountFollow, t)
|
||||
// Target sends to actor he accepted the follow request
|
||||
return sendAccept(actorFollow, t)
|
||||
})
|
||||
|
||||
logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL)
|
||||
logger.info('Actor uuid %s is followed by actor %s.', actor.url, targetActorURL)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { ActivityLike } from '../../../../shared/models/activitypub'
|
||||
import { retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
|
||||
async function processLikeActivity (activity: ActivityLike) {
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
return processLikeVideo(account, activity)
|
||||
return processLikeVideo(actor, activity)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -21,18 +21,21 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) {
|
||||
async function processLikeVideo (actor: ActorModel, activity: ActivityLike) {
|
||||
const options = {
|
||||
arguments: [ byAccount, activity ],
|
||||
arguments: [ actor, activity ],
|
||||
errorMessage: 'Cannot like the video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(createVideoLike, options)
|
||||
}
|
||||
|
||||
function createVideoLike (byAccount: AccountModel, activity: ActivityLike) {
|
||||
function createVideoLike (byActor: ActorModel, activity: ActivityLike) {
|
||||
const videoUrl = activity.object
|
||||
|
||||
const byAccount = byActor.Account
|
||||
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl)
|
||||
|
||||
|
@ -52,7 +55,7 @@ function createVideoLike (byAccount: AccountModel, activity: ActivityLike) {
|
|||
|
||||
if (video.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
const exceptions = [ byActor ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -3,8 +3,9 @@ import { DislikeObject } from '../../../../shared/models/activitypub/objects'
|
|||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
|
||||
|
@ -32,21 +33,21 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processUndoLike (actor: string, activity: ActivityUndo) {
|
||||
function processUndoLike (actorUrl: string, activity: ActivityUndo) {
|
||||
const options = {
|
||||
arguments: [ actor, activity ],
|
||||
arguments: [ actorUrl, activity ],
|
||||
errorMessage: 'Cannot undo like with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(undoLike, options)
|
||||
}
|
||||
|
||||
function undoLike (actor: string, activity: ActivityUndo) {
|
||||
function undoLike (actorUrl: string, activity: ActivityUndo) {
|
||||
const likeActivity = activity.object as ActivityLike
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const byAccount = await AccountModel.loadByUrl(actor, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actor)
|
||||
const byAccount = await AccountModel.loadByUrl(actorUrl, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actorUrl)
|
||||
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t)
|
||||
if (!video) throw new Error('Unknown video ' + likeActivity.actor)
|
||||
|
@ -59,27 +60,27 @@ function undoLike (actor: string, activity: ActivityUndo) {
|
|||
|
||||
if (video.isOwned()) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
const exceptions = [ byAccount.Actor ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function processUndoDislike (actor: string, activity: ActivityUndo) {
|
||||
function processUndoDislike (actorUrl: string, activity: ActivityUndo) {
|
||||
const options = {
|
||||
arguments: [ actor, activity ],
|
||||
arguments: [ actorUrl, activity ],
|
||||
errorMessage: 'Cannot undo dislike with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(undoDislike, options)
|
||||
}
|
||||
|
||||
function undoDislike (actor: string, activity: ActivityUndo) {
|
||||
function undoDislike (actorUrl: string, activity: ActivityUndo) {
|
||||
const dislike = activity.object.object as DislikeObject
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const byAccount = await AccountModel.loadByUrl(actor, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actor)
|
||||
const byAccount = await AccountModel.loadByUrl(actorUrl, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actorUrl)
|
||||
|
||||
const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
|
||||
if (!video) throw new Error('Unknown video ' + dislike.actor)
|
||||
|
@ -92,30 +93,30 @@ function undoDislike (actor: string, activity: ActivityUndo) {
|
|||
|
||||
if (video.isOwned()) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
const exceptions = [ byAccount.Actor ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function processUndoFollow (actor: string, followActivity: ActivityFollow) {
|
||||
function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
|
||||
const options = {
|
||||
arguments: [ actor, followActivity ],
|
||||
arguments: [ actorUrl, followActivity ],
|
||||
errorMessage: 'Cannot undo follow with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(undoFollow, options)
|
||||
}
|
||||
|
||||
function undoFollow (actor: string, followActivity: ActivityFollow) {
|
||||
function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const follower = await AccountModel.loadByUrl(actor, t)
|
||||
const following = await AccountModel.loadByUrl(followActivity.object, t)
|
||||
const accountFollow = await AccountFollowModel.loadByAccountAndTarget(follower.id, following.id, t)
|
||||
const follower = await ActorModel.loadByUrl(actorUrl, t)
|
||||
const following = await ActorModel.loadByUrl(followActivity.object, t)
|
||||
const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
|
||||
|
||||
if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
|
||||
if (!actorFollow) throw new Error(`'Unknown actor follow ${follower.id} -> ${following.id}.`)
|
||||
|
||||
await accountFollow.destroy({ transaction: t })
|
||||
await actorFollow.destroy({ transaction: t })
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
|
||||
import { ActivityUpdate } from '../../../../shared/models/activitypub'
|
||||
import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { TagModel } from '../../../models/video/tag'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoFileModel } from '../../../models/video/video-file'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||
|
||||
async function processUpdateActivity (activity: ActivityUpdate) {
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
const actor = await getOrCreateActorAndServerAndModel(activity.actor)
|
||||
|
||||
if (activity.object.type === 'Video') {
|
||||
return processUpdateVideo(account, activity.object)
|
||||
} else if (activity.object.type === 'VideoChannel') {
|
||||
return processUpdateVideoChannel(account, activity.object)
|
||||
return processUpdateVideo(actor, activity)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -31,16 +27,18 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) {
|
||||
function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||
const options = {
|
||||
arguments: [ account, video ],
|
||||
arguments: [ actor, activity ],
|
||||
errorMessage: 'Cannot update the remote video with many retries'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(updateRemoteVideo, options)
|
||||
}
|
||||
|
||||
async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) {
|
||||
async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||
const videoAttributesToUpdate = activity.object
|
||||
|
||||
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
|
||||
let videoInstance: VideoModel
|
||||
let videoFieldsSave: object
|
||||
|
@ -54,23 +52,23 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
|
|||
const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
|
||||
if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
|
||||
|
||||
if (videoInstance.VideoChannel.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url)
|
||||
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(videoInstance.VideoChannel, videoAttributesToUpdate)
|
||||
const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
|
||||
videoInstance.set('name', videoData.name)
|
||||
videoInstance.set('category', videoData.category)
|
||||
videoInstance.set('licence', videoData.licence)
|
||||
videoInstance.set('language', videoData.language)
|
||||
videoInstance.set('nsfw', videoData.nsfw)
|
||||
videoInstance.set('privacy', videoData.privacy)
|
||||
videoInstance.set('description', videoData.description)
|
||||
videoInstance.set('duration', videoData.duration)
|
||||
videoInstance.set('createdAt', videoData.createdAt)
|
||||
videoInstance.set('updatedAt', videoData.updatedAt)
|
||||
videoInstance.set('views', videoData.views)
|
||||
// videoInstance.set('likes', videoData.likes)
|
||||
// videoInstance.set('dislikes', videoData.dislikes)
|
||||
|
||||
await videoInstance.save(sequelizeOptions)
|
||||
|
||||
|
@ -101,36 +99,3 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
|
|||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function processUpdateVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannel ],
|
||||
errorMessage: 'Cannot update the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(updateRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
async function updateRemoteVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
|
||||
logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
const sequelizeOptions = { transaction: t }
|
||||
|
||||
const videoChannelInstance = await VideoChannelModel.loadByUrl(videoChannel.id)
|
||||
if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
|
||||
|
||||
if (videoChannelInstance.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.id + ' does not own video channel ' + videoChannelInstance.url)
|
||||
}
|
||||
|
||||
videoChannelInstance.set('name', videoChannel.name)
|
||||
videoChannelInstance.set('description', videoChannel.content)
|
||||
videoChannelInstance.set('createdAt', videoChannel.published)
|
||||
videoChannelInstance.set('updatedAt', videoChannel.updated)
|
||||
|
||||
await videoChannelInstance.save(sequelizeOptions)
|
||||
})
|
||||
|
||||
logger.info('Remote video channel with uuid %s updated', videoChannel.uuid)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
||||
import { logger } from '../../../helpers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { processAcceptActivity } from './process-accept'
|
||||
import { processAddActivity } from './process-add'
|
||||
import { processAnnounceActivity } from './process-announce'
|
||||
import { processCreateActivity } from './process-create'
|
||||
import { processDeleteActivity } from './process-delete'
|
||||
|
@ -11,9 +10,8 @@ import { processLikeActivity } from './process-like'
|
|||
import { processUndoActivity } from './process-undo'
|
||||
import { processUpdateActivity } from './process-update'
|
||||
|
||||
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = {
|
||||
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxActor?: ActorModel) => Promise<any> } = {
|
||||
Create: processCreateActivity,
|
||||
Add: processAddActivity,
|
||||
Update: processUpdateActivity,
|
||||
Delete: processDeleteActivity,
|
||||
Follow: processFollowActivity,
|
||||
|
@ -23,11 +21,11 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
|
|||
Like: processLikeActivity
|
||||
}
|
||||
|
||||
async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) {
|
||||
async function processActivities (activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel) {
|
||||
for (const activity of activities) {
|
||||
// When we fetch remote data, we don't have signature
|
||||
if (signatureAccount && activity.actor !== signatureAccount.url) {
|
||||
logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureAccount.url)
|
||||
if (signatureActor && activity.actor !== signatureActor.url) {
|
||||
logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureActor.url)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -38,7 +36,7 @@ async function processActivities (activities: Activity[], signatureAccount?: Acc
|
|||
}
|
||||
|
||||
try {
|
||||
await activityProcessor(activity, inboxAccount)
|
||||
await activityProcessor(activity, inboxActor)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot process activity %s.', activity.type, err)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export * from './send-accept'
|
||||
export * from './send-add'
|
||||
export * from './send-announce'
|
||||
export * from './send-create'
|
||||
export * from './send-delete'
|
||||
|
|
|
@ -2,18 +2,16 @@ import { Transaction } from 'sequelize'
|
|||
import { Activity } from '../../../../shared/models/activitypub'
|
||||
import { logger } from '../../../helpers'
|
||||
import { ACTIVITY_PUB } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler'
|
||||
|
||||
async function forwardActivity (
|
||||
activity: Activity,
|
||||
t: Transaction,
|
||||
followersException: AccountModel[] = []
|
||||
followersException: ActorModel[] = []
|
||||
) {
|
||||
const to = activity.to || []
|
||||
const cc = activity.cc || []
|
||||
|
@ -25,11 +23,11 @@ async function forwardActivity (
|
|||
}
|
||||
}
|
||||
|
||||
const toAccountFollowers = await AccountModel.listByFollowersUrls(followersUrls, t)
|
||||
const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
|
||||
const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
|
||||
const uris = await computeFollowerUris(toActorFollowers, followersException, t)
|
||||
|
||||
if (uris.length === 0) {
|
||||
logger.info('0 followers for %s, no forwarding.', toAccountFollowers.map(a => a.id).join(', '))
|
||||
logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
@ -45,14 +43,14 @@ async function forwardActivity (
|
|||
|
||||
async function broadcastToFollowers (
|
||||
data: any,
|
||||
byAccount: AccountModel,
|
||||
toAccountFollowers: AccountModel[],
|
||||
byActor: ActorModel,
|
||||
toActorFollowers: ActorModel[],
|
||||
t: Transaction,
|
||||
followersException: AccountModel[] = []
|
||||
followersException: ActorModel[] = []
|
||||
) {
|
||||
const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
|
||||
const uris = await computeFollowerUris(toActorFollowers, followersException, t)
|
||||
if (uris.length === 0) {
|
||||
logger.info('0 followers for %s, no broadcasting.', toAccountFollowers.map(a => a.id).join(', '))
|
||||
logger.info('0 followers for %s, no broadcasting.', toActorFollowers.map(a => a.id).join(', '))
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
@ -60,62 +58,48 @@ async function broadcastToFollowers (
|
|||
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris,
|
||||
signatureAccountId: byAccount.id,
|
||||
signatureActorId: byActor.id,
|
||||
body: data
|
||||
}
|
||||
|
||||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
|
||||
}
|
||||
|
||||
async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) {
|
||||
logger.debug('Creating unicast job.', { uri: toAccountUrl })
|
||||
async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string, t: Transaction) {
|
||||
logger.debug('Creating unicast job.', { uri: toActorUrl })
|
||||
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris: [ toAccountUrl ],
|
||||
signatureAccountId: byAccount.id,
|
||||
uris: [ toActorUrl ],
|
||||
signatureActorId: byActor.id,
|
||||
body: data
|
||||
}
|
||||
|
||||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
|
||||
}
|
||||
|
||||
function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) {
|
||||
function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
|
||||
return {
|
||||
to: [ video.VideoChannel.Account.url ],
|
||||
cc: accountsInvolvedInVideo.map(a => a.followersUrl)
|
||||
to: [ video.VideoChannel.Account.Actor.url ],
|
||||
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) {
|
||||
function getObjectFollowersAudience (actorsInvolvedInObject: ActorModel[]) {
|
||||
return {
|
||||
to: [ videoChannel.Account.url ],
|
||||
cc: accountsInvolved.map(a => a.followersUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) {
|
||||
return {
|
||||
to: accountsInvolvedInObject.map(a => a.followersUrl),
|
||||
to: actorsInvolvedInObject.map(a => a.followersUrl),
|
||||
cc: []
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) {
|
||||
const accountsToForwardView = await VideoShareModel.loadAccountsByShare(video.id, t)
|
||||
accountsToForwardView.push(video.VideoChannel.Account)
|
||||
async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
|
||||
const actorsToForwardView = await VideoShareModel.loadActorsByShare(video.id, t)
|
||||
actorsToForwardView.push(video.VideoChannel.Account.Actor)
|
||||
|
||||
return accountsToForwardView
|
||||
return actorsToForwardView
|
||||
}
|
||||
|
||||
async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
|
||||
accountsToForwardView.push(videoChannel.Account)
|
||||
|
||||
return accountsToForwardView
|
||||
}
|
||||
|
||||
async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) {
|
||||
const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t)
|
||||
async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) {
|
||||
const followerInboxUrls = await actorSender.getFollowerSharedInboxUrls(t)
|
||||
|
||||
// Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
|
||||
let to = []
|
||||
|
@ -132,10 +116,10 @@ async function getAudience (accountSender: AccountModel, t: Transaction, isPubli
|
|||
return { to, cc }
|
||||
}
|
||||
|
||||
async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) {
|
||||
const toAccountFollowerIds = toAccountFollower.map(a => a.id)
|
||||
async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) {
|
||||
const toActorFollowerIds = toActorFollower.map(a => a.id)
|
||||
|
||||
const result = await AccountFollowModel.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t)
|
||||
const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
|
||||
const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
|
||||
return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
|
||||
}
|
||||
|
@ -144,12 +128,10 @@ async function computeFollowerUris (toAccountFollower: AccountModel[], followers
|
|||
|
||||
export {
|
||||
broadcastToFollowers,
|
||||
getOriginVideoChannelAudience,
|
||||
unicastTo,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getAccountsInvolvedInVideo,
|
||||
getAccountsInvolvedInVideoChannel,
|
||||
getActorsInvolvedInVideo,
|
||||
getObjectFollowersAudience,
|
||||
forwardActivity
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityAccept } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { getAccountFollowAcceptActivityPubUrl } from '../url'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { getActorFollowAcceptActivityPubUrl } from '../url'
|
||||
import { unicastTo } from './misc'
|
||||
|
||||
async function sendAccept (accountFollow: AccountFollowModel, t: Transaction) {
|
||||
const follower = accountFollow.AccountFollower
|
||||
const me = accountFollow.AccountFollowing
|
||||
async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
|
||||
const follower = actorFollow.ActorFollower
|
||||
const me = actorFollow.ActorFollowing
|
||||
|
||||
const url = getAccountFollowAcceptActivityPubUrl(accountFollow)
|
||||
const url = getActorFollowAcceptActivityPubUrl(actorFollow)
|
||||
const data = acceptActivityData(url, me)
|
||||
|
||||
return unicastTo(data, me, follower.inboxUrl, t)
|
||||
|
@ -23,12 +23,10 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function acceptActivityData (url: string, byAccount: AccountModel) {
|
||||
const activity: ActivityAccept = {
|
||||
function acceptActivityData (url: string, byActor: ActorModel): ActivityAccept {
|
||||
return {
|
||||
type: 'Accept',
|
||||
id: url,
|
||||
actor: byAccount.url
|
||||
actor: byActor.url
|
||||
}
|
||||
|
||||
return activity
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityAdd } from '../../../../shared/models/activitypub'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { broadcastToFollowers, getAudience } from './misc'
|
||||
|
||||
async function sendAddVideo (video: VideoModel, t: Transaction) {
|
||||
const byAccount = video.VideoChannel.Account
|
||||
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject, t)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, [ byAccount ], t)
|
||||
}
|
||||
|
||||
async function addActivityData (
|
||||
url: string,
|
||||
byAccount: AccountModel,
|
||||
video: VideoModel,
|
||||
target: string,
|
||||
object: any,
|
||||
t: Transaction
|
||||
): Promise<ActivityAdd> {
|
||||
const videoPublic = video.privacy === VideoPrivacy.PUBLIC
|
||||
|
||||
const { to, cc } = await getAudience(byAccount, t, videoPublic)
|
||||
|
||||
return {
|
||||
type: 'Add',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
to,
|
||||
cc,
|
||||
object,
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
addActivityData,
|
||||
sendAddVideo
|
||||
}
|
|
@ -1,88 +1,59 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityAdd } from '../../../../shared/index'
|
||||
import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { getAnnounceActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsInvolvedInVideo,
|
||||
getAccountsInvolvedInVideoChannel,
|
||||
getActorsInvolvedInVideo,
|
||||
getAudience,
|
||||
getObjectFollowersAudience,
|
||||
getOriginVideoAudience,
|
||||
getOriginVideoChannelAudience,
|
||||
unicastTo
|
||||
} from './misc'
|
||||
import { addActivityData } from './send-add'
|
||||
import { createActivityData } from './send-create'
|
||||
|
||||
async function buildVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(video.url, byAccount)
|
||||
async function buildVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(video.url, byActor)
|
||||
const videoObject = video.toActivityPubObject()
|
||||
|
||||
const videoChannel = video.VideoChannel
|
||||
const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject(), t)
|
||||
const announcedAudience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
|
||||
const announcedActivity = await createActivityData(url, video.VideoChannel.Account.Actor, videoObject, t, announcedAudience)
|
||||
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideo(video, t)
|
||||
const accountsToForwardView = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(accountsToForwardView)
|
||||
return announceActivityData(url, byAccount, announcedActivity, t, audience)
|
||||
return announceActivityData(url, byActor, announcedActivity, t, audience)
|
||||
}
|
||||
|
||||
async function sendVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const data = await buildVideoAnnounceToFollowers(byAccount, video, t)
|
||||
async function sendVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const data = await buildVideoAnnounceToFollowers(byActor, video, t)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, [ byAccount ], t)
|
||||
return broadcastToFollowers(data, byActor, [ byActor ], t)
|
||||
}
|
||||
|
||||
async function sendVideoAnnounceToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(video.url, byAccount)
|
||||
async function sendVideoAnnounceToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(video.url, byActor)
|
||||
|
||||
const videoChannel = video.VideoChannel
|
||||
const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject(), t)
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const announcedActivity = await createActivityData(url, video.VideoChannel.Account.Actor, videoObject, t)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, announcedActivity, t, audience)
|
||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byActor, announcedActivity, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function buildVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
|
||||
const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
|
||||
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel, t)
|
||||
const audience = getObjectFollowersAudience(accountsToForwardView)
|
||||
return announceActivityData(url, byAccount, announcedActivity, t, audience)
|
||||
}
|
||||
|
||||
async function sendVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, [ byAccount ], t)
|
||||
}
|
||||
|
||||
async function sendVideoChannelAnnounceToOrigin (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
|
||||
const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideoChannel(videoChannel, t)
|
||||
const audience = getOriginVideoChannelAudience(videoChannel, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, announcedActivity, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function announceActivityData (
|
||||
url: string,
|
||||
byAccount: AccountModel,
|
||||
object: ActivityCreate | ActivityAdd,
|
||||
byActor: ActorModel,
|
||||
object: ActivityCreate,
|
||||
t: Transaction,
|
||||
audience?: ActivityAudience
|
||||
): Promise<ActivityAnnounce> {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount, t)
|
||||
audience = await getAudience(byActor, t)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -90,7 +61,7 @@ async function announceActivityData (
|
|||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
object
|
||||
}
|
||||
}
|
||||
|
@ -99,10 +70,7 @@ async function announceActivityData (
|
|||
|
||||
export {
|
||||
sendVideoAnnounceToFollowers,
|
||||
sendVideoChannelAnnounceToFollowers,
|
||||
sendVideoAnnounceToOrigin,
|
||||
sendVideoChannelAnnounceToOrigin,
|
||||
announceActivityData,
|
||||
buildVideoAnnounceToFollowers,
|
||||
buildVideoChannelAnnounceToFollowers
|
||||
buildVideoAnnounceToFollowers
|
||||
}
|
||||
|
|
|
@ -1,111 +1,112 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
|
||||
import { getServerAccount } from '../../../helpers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { getServerActor } from '../../../helpers'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoAbuseModel } from '../../../models/video/video-abuse'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsInvolvedInVideo,
|
||||
getActorsInvolvedInVideo,
|
||||
getAudience,
|
||||
getObjectFollowersAudience,
|
||||
getOriginVideoAudience,
|
||||
unicastTo
|
||||
} from './misc'
|
||||
|
||||
async function sendCreateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const byAccount = videoChannel.Account
|
||||
async function sendCreateVideo (video: VideoModel, t: Transaction) {
|
||||
const byActor = video.VideoChannel.Account.Actor
|
||||
|
||||
const videoChannelObject = videoChannel.toActivityPubObject()
|
||||
const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject, t)
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
|
||||
const data = await createActivityData(video.url, byActor, videoObject, t, audience)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, [ byAccount ], t)
|
||||
return broadcastToFollowers(data, byActor, [ byActor ], t)
|
||||
}
|
||||
|
||||
async function sendVideoAbuse (byAccount: AccountModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
|
||||
async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoAbuseActivityPubUrl(videoAbuse)
|
||||
|
||||
const audience = { to: [ video.VideoChannel.Account.url ], cc: [] }
|
||||
const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject(), t, audience)
|
||||
const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
|
||||
const data = await createActivityData(url, byActor, videoAbuse.toActivityPubObject(), t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendCreateViewToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||
const viewActivity = createViewActivityData(byAccount, video)
|
||||
async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoViewActivityPubUrl(byActor, video)
|
||||
const viewActivity = createViewActivityData(byActor, video)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, viewActivity, t, audience)
|
||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byActor, viewActivity, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendCreateViewToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||
const viewActivity = createViewActivityData(byAccount, video)
|
||||
async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoViewActivityPubUrl(byActor, video)
|
||||
const viewActivity = createViewActivityData(byActor, video)
|
||||
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(accountsToForwardView)
|
||||
const data = await createActivityData(url, byAccount, viewActivity, t, audience)
|
||||
const actorsToForwardView = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(actorsToForwardView)
|
||||
const data = await createActivityData(url, byActor, viewActivity, t, audience)
|
||||
|
||||
// Use the server account to send the view, because it could be an unregistered account
|
||||
const serverAccount = await getServerAccount()
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
// Use the server actor to send the view
|
||||
const serverActor = await getServerActor()
|
||||
const followersException = [ byActor ]
|
||||
return broadcastToFollowers(data, serverActor, actorsToForwardView, t, followersException)
|
||||
}
|
||||
|
||||
async function sendCreateDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoDislikeActivityPubUrl(byActor, video)
|
||||
const dislikeActivity = createDislikeActivityData(byActor, video)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, dislikeActivity, t, audience)
|
||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byActor, dislikeActivity, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendCreateDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoDislikeActivityPubUrl(byActor, video)
|
||||
const dislikeActivity = createDislikeActivityData(byActor, video)
|
||||
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(accountsToForwardView)
|
||||
const data = await createActivityData(url, byAccount, dislikeActivity, t, audience)
|
||||
const actorsToForwardView = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(actorsToForwardView)
|
||||
const data = await createActivityData(url, byActor, dislikeActivity, t, audience)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException)
|
||||
const followersException = [ byActor ]
|
||||
return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException)
|
||||
}
|
||||
|
||||
async function createActivityData (
|
||||
url: string,
|
||||
byAccount: AccountModel,
|
||||
byActor: ActorModel,
|
||||
object: any,
|
||||
t: Transaction,
|
||||
audience?: ActivityAudience
|
||||
): Promise<ActivityCreate> {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount, t)
|
||||
audience = await getAudience(byActor, t)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Create',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
object
|
||||
}
|
||||
}
|
||||
|
||||
function createDislikeActivityData (byAccount: AccountModel, video: VideoModel) {
|
||||
function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
|
||||
return {
|
||||
type: 'Dislike',
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
object: video.url
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +114,7 @@ function createDislikeActivityData (byAccount: AccountModel, video: VideoModel)
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
sendCreateVideoChannel,
|
||||
sendCreateVideo,
|
||||
sendVideoAbuse,
|
||||
createActivityData,
|
||||
sendCreateViewToOrigin,
|
||||
|
@ -125,10 +126,10 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createViewActivityData (byAccount: AccountModel, video: VideoModel) {
|
||||
function createViewActivityData (byActor: ActorModel, video: VideoModel) {
|
||||
return {
|
||||
type: 'View',
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
object: video.url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,40 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityDelete } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { broadcastToFollowers } from './misc'
|
||||
|
||||
async function sendDeleteVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const byAccount = videoChannel.Account
|
||||
|
||||
const data = deleteActivityData(videoChannel.url, byAccount)
|
||||
|
||||
const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
|
||||
accountsInvolved.push(byAccount)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, accountsInvolved, t)
|
||||
}
|
||||
|
||||
async function sendDeleteVideo (video: VideoModel, t: Transaction) {
|
||||
const byAccount = video.VideoChannel.Account
|
||||
const byActor = video.VideoChannel.Account.Actor
|
||||
|
||||
const data = deleteActivityData(video.url, byAccount)
|
||||
const data = deleteActivityData(video.url, byActor)
|
||||
|
||||
const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t)
|
||||
accountsInvolved.push(byAccount)
|
||||
const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t)
|
||||
actorsInvolved.push(byActor)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, accountsInvolved, t)
|
||||
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
||||
}
|
||||
|
||||
async function sendDeleteAccount (account: AccountModel, t: Transaction) {
|
||||
const data = deleteActivityData(account.url, account)
|
||||
async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
|
||||
const data = deleteActivityData(byActor.url, byActor)
|
||||
|
||||
return broadcastToFollowers(data, account, [ account ], t)
|
||||
return broadcastToFollowers(data, byActor, [ byActor ], t)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
sendDeleteVideoChannel,
|
||||
sendDeleteVideo,
|
||||
sendDeleteAccount
|
||||
sendDeleteActor
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function deleteActivityData (url: string, byAccount: AccountModel): ActivityDelete {
|
||||
function deleteActivityData (url: string, byActor: ActorModel): ActivityDelete {
|
||||
return {
|
||||
type: 'Delete',
|
||||
id: url,
|
||||
actor: byAccount.url
|
||||
actor: byActor.url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityFollow } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { getAccountFollowActivityPubUrl } from '../url'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { getActorFollowActivityPubUrl } from '../url'
|
||||
import { unicastTo } from './misc'
|
||||
|
||||
function sendFollow (accountFollow: AccountFollowModel, t: Transaction) {
|
||||
const me = accountFollow.AccountFollower
|
||||
const following = accountFollow.AccountFollowing
|
||||
function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
|
||||
const me = actorFollow.ActorFollower
|
||||
const following = actorFollow.ActorFollowing
|
||||
|
||||
const url = getAccountFollowActivityPubUrl(accountFollow)
|
||||
const url = getActorFollowActivityPubUrl(actorFollow)
|
||||
const data = followActivityData(url, me, following)
|
||||
|
||||
return unicastTo(data, me, following.inboxUrl, t)
|
||||
}
|
||||
|
||||
function followActivityData (url: string, byAccount: AccountModel, targetAccount: AccountModel): ActivityFollow {
|
||||
function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow {
|
||||
return {
|
||||
type: 'Follow',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
object: targetAccount.url
|
||||
actor: byActor.url,
|
||||
object: targetActor.url
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { getVideoLikeActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsInvolvedInVideo,
|
||||
getActorsInvolvedInVideo,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getObjectFollowersAudience,
|
||||
getOriginVideoAudience,
|
||||
unicastTo
|
||||
} from './misc'
|
||||
|
||||
async function sendLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
async function sendLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byActor, video)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await likeActivityData(url, byAccount, video, t, audience)
|
||||
const data = await likeActivityData(url, byActor, video, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
async function sendLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byActor, video)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
|
||||
const data = await likeActivityData(url, byAccount, video, t, audience)
|
||||
const data = await likeActivityData(url, byActor, video, t, audience)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, byAccount, accountsInvolvedInVideo, t, followersException)
|
||||
const followersException = [ byActor ]
|
||||
return broadcastToFollowers(data, byActor, accountsInvolvedInVideo, t, followersException)
|
||||
}
|
||||
|
||||
async function likeActivityData (
|
||||
url: string,
|
||||
byAccount: AccountModel,
|
||||
byActor: ActorModel,
|
||||
video: VideoModel,
|
||||
t: Transaction,
|
||||
audience?: ActivityAudience
|
||||
): Promise<ActivityLike> {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount, t)
|
||||
audience = await getAudience(byActor, t)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Like',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
object: video.url
|
||||
|
|
|
@ -6,13 +6,13 @@ import {
|
|||
ActivityLike,
|
||||
ActivityUndo
|
||||
} from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { AccountFollowModel } from '../../../models/account/account-follow'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||
import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsInvolvedInVideo,
|
||||
getActorsInvolvedInVideo,
|
||||
getAudience,
|
||||
getObjectFollowersAudience,
|
||||
getOriginVideoAudience,
|
||||
|
@ -22,11 +22,11 @@ import { createActivityData, createDislikeActivityData } from './send-create'
|
|||
import { followActivityData } from './send-follow'
|
||||
import { likeActivityData } from './send-like'
|
||||
|
||||
async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction) {
|
||||
const me = accountFollow.AccountFollower
|
||||
const following = accountFollow.AccountFollowing
|
||||
async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
|
||||
const me = actorFollow.ActorFollower
|
||||
const following = actorFollow.ActorFollowing
|
||||
|
||||
const followUrl = getAccountFollowActivityPubUrl(accountFollow)
|
||||
const followUrl = getActorFollowActivityPubUrl(actorFollow)
|
||||
const undoUrl = getUndoActivityPubUrl(followUrl)
|
||||
|
||||
const object = followActivityData(followUrl, me, following)
|
||||
|
@ -35,58 +35,58 @@ async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction
|
|||
return unicastTo(data, me, following.inboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendUndoLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
|
||||
const undoUrl = getUndoActivityPubUrl(likeUrl)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const object = await likeActivityData(likeUrl, byAccount, video, t)
|
||||
const data = await undoActivityData(undoUrl, byAccount, object, t, audience)
|
||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||
const object = await likeActivityData(likeUrl, byActor, video, t)
|
||||
const data = await undoActivityData(undoUrl, byActor, object, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendUndoLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
async function sendUndoLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
|
||||
const undoUrl = getUndoActivityPubUrl(likeUrl)
|
||||
|
||||
const toAccountsFollowers = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(toAccountsFollowers)
|
||||
const object = await likeActivityData(likeUrl, byAccount, video, t)
|
||||
const data = await undoActivityData(undoUrl, byAccount, object, t, audience)
|
||||
const toActorsFollowers = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getObjectFollowersAudience(toActorsFollowers)
|
||||
const object = await likeActivityData(likeUrl, byActor, video, t)
|
||||
const data = await undoActivityData(undoUrl, byActor, object, t, audience)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
|
||||
const followersException = [ byActor ]
|
||||
return broadcastToFollowers(data, byActor, toActorsFollowers, t, followersException)
|
||||
}
|
||||
|
||||
async function sendUndoDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
async function sendUndoDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
|
||||
const undoUrl = getUndoActivityPubUrl(dislikeUrl)
|
||||
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
const object = await createActivityData(undoUrl, byAccount, dislikeActivity, t)
|
||||
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
|
||||
const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
|
||||
const dislikeActivity = createDislikeActivityData(byActor, video)
|
||||
const object = await createActivityData(undoUrl, byActor, dislikeActivity, t)
|
||||
|
||||
const data = await undoActivityData(undoUrl, byAccount, object, t, audience)
|
||||
const data = await undoActivityData(undoUrl, byActor, object, t, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
|
||||
}
|
||||
|
||||
async function sendUndoDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
|
||||
const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
async function sendUndoDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
|
||||
const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
|
||||
const undoUrl = getUndoActivityPubUrl(dislikeUrl)
|
||||
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
const object = await createActivityData(undoUrl, byAccount, dislikeActivity, t)
|
||||
const dislikeActivity = createDislikeActivityData(byActor, video)
|
||||
const object = await createActivityData(undoUrl, byActor, dislikeActivity, t)
|
||||
|
||||
const data = await undoActivityData(undoUrl, byAccount, object, t)
|
||||
const data = await undoActivityData(undoUrl, byActor, object, t)
|
||||
|
||||
const toAccountsFollowers = await getAccountsInvolvedInVideo(video, t)
|
||||
const toActorsFollowers = await getActorsInvolvedInVideo(video, t)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
|
||||
const followersException = [ byActor ]
|
||||
return broadcastToFollowers(data, byActor, toActorsFollowers, t, followersException)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -103,19 +103,19 @@ export {
|
|||
|
||||
async function undoActivityData (
|
||||
url: string,
|
||||
byAccount: AccountModel,
|
||||
byActor: ActorModel,
|
||||
object: ActivityFollow | ActivityLike | ActivityCreate,
|
||||
t: Transaction,
|
||||
audience?: ActivityAudience
|
||||
): Promise<ActivityUndo> {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount, t)
|
||||
audience = await getAudience(byActor, t)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Undo',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
actor: byActor.url,
|
||||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
object
|
||||
|
|
|
@ -1,56 +1,52 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityUpdate } from '../../../../shared/models/activitypub'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { getUpdateActivityPubUrl } from '../url'
|
||||
import { broadcastToFollowers, getAudience } from './misc'
|
||||
|
||||
async function sendUpdateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const byAccount = videoChannel.Account
|
||||
|
||||
const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString())
|
||||
const videoChannelObject = videoChannel.toActivityPubObject()
|
||||
const data = await updateActivityData(url, byAccount, videoChannelObject, t)
|
||||
|
||||
const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
|
||||
accountsInvolved.push(byAccount)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, accountsInvolved, t)
|
||||
}
|
||||
|
||||
async function sendUpdateVideo (video: VideoModel, t: Transaction) {
|
||||
const byAccount = video.VideoChannel.Account
|
||||
const byActor = video.VideoChannel.Account.Actor
|
||||
|
||||
const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const data = await updateActivityData(url, byAccount, videoObject, t)
|
||||
const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
|
||||
|
||||
const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t)
|
||||
accountsInvolved.push(byAccount)
|
||||
const data = await updateActivityData(url, byActor, videoObject, t, audience)
|
||||
|
||||
return broadcastToFollowers(data, byAccount, accountsInvolved, t)
|
||||
const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t)
|
||||
actorsInvolved.push(byActor)
|
||||
|
||||
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
sendUpdateVideoChannel,
|
||||
sendUpdateVideo
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function updateActivityData (url: string, byAccount: AccountModel, object: any, t: Transaction): Promise<ActivityUpdate> {
|
||||
const { to, cc } = await getAudience(byAccount, t)
|
||||
async function updateActivityData (
|
||||
url: string,
|
||||
byActor: ActorModel,
|
||||
object: any,
|
||||
t: Transaction,
|
||||
audience?: ActivityAudience
|
||||
): Promise<ActivityUpdate> {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byActor, t)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Update',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
to,
|
||||
cc,
|
||||
actor: byActor.url,
|
||||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
object
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,20 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { getServerAccount } from '../../helpers'
|
||||
import { getServerActor } from '../../helpers'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../models/video/video-channel-share'
|
||||
import { VideoShareModel } from '../../models/video/video-share'
|
||||
import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send'
|
||||
|
||||
async function shareVideoChannelByServer (videoChannel: VideoChannelModel, t: Transaction) {
|
||||
const serverAccount = await getServerAccount()
|
||||
|
||||
await VideoChannelShareModel.create({
|
||||
accountId: serverAccount.id,
|
||||
videoChannelId: videoChannel.id
|
||||
}, { transaction: t })
|
||||
|
||||
return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t)
|
||||
}
|
||||
import { sendVideoAnnounceToFollowers } from './send'
|
||||
|
||||
async function shareVideoByServer (video: VideoModel, t: Transaction) {
|
||||
const serverAccount = await getServerAccount()
|
||||
const serverActor = await getServerActor()
|
||||
|
||||
await VideoShareModel.create({
|
||||
accountId: serverAccount.id,
|
||||
actorId: serverActor.id,
|
||||
videoId: video.id
|
||||
}, { transaction: t })
|
||||
|
||||
return sendVideoAnnounceToFollowers(serverAccount, video, t)
|
||||
return sendVideoAnnounceToFollowers(serverActor, video, t)
|
||||
}
|
||||
|
||||
export {
|
||||
shareVideoChannelByServer,
|
||||
shareVideoByServer
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { CONFIG } from '../../initializers'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { AccountFollowModel } from '../../models/account/account-follow'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoAbuseModel } from '../../models/video/video-abuse'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
|
||||
function getVideoActivityPubUrl (video: VideoModel) {
|
||||
return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
|
||||
}
|
||||
|
||||
function getVideoChannelActivityPubUrl (videoChannel: VideoChannelModel) {
|
||||
return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid
|
||||
function getVideoChannelActivityPubUrl (videoChannelUUID: string) {
|
||||
return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID
|
||||
}
|
||||
|
||||
function getApplicationActivityPubUrl () {
|
||||
return CONFIG.WEBSERVER.URL + '/application/peertube'
|
||||
}
|
||||
|
||||
function getAccountActivityPubUrl (accountName: string) {
|
||||
|
@ -21,34 +24,34 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
|
|||
return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
|
||||
}
|
||||
|
||||
function getVideoViewActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
|
||||
return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString()
|
||||
function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
||||
return video.url + '/views/' + byActor.uuid + '/' + new Date().toISOString()
|
||||
}
|
||||
|
||||
function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
|
||||
return byAccount.url + '/likes/' + video.id
|
||||
function getVideoLikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
||||
return byActor.url + '/likes/' + video.id
|
||||
}
|
||||
|
||||
function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
|
||||
return byAccount.url + '/dislikes/' + video.id
|
||||
function getVideoDislikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
||||
return byActor.url + '/dislikes/' + video.id
|
||||
}
|
||||
|
||||
function getAccountFollowActivityPubUrl (accountFollow: AccountFollowModel) {
|
||||
const me = accountFollow.AccountFollower
|
||||
const following = accountFollow.AccountFollowing
|
||||
function getActorFollowActivityPubUrl (actorFollow: ActorFollowModel) {
|
||||
const me = actorFollow.ActorFollower
|
||||
const following = actorFollow.ActorFollowing
|
||||
|
||||
return me.url + '/follows/' + following.id
|
||||
}
|
||||
|
||||
function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowModel) {
|
||||
const follower = accountFollow.AccountFollower
|
||||
const me = accountFollow.AccountFollowing
|
||||
function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) {
|
||||
const follower = actorFollow.ActorFollower
|
||||
const me = actorFollow.ActorFollowing
|
||||
|
||||
return follower.url + '/accepts/follows/' + me.id
|
||||
}
|
||||
|
||||
function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountModel) {
|
||||
return originalUrl + '/announces/' + byAccount.id
|
||||
function getAnnounceActivityPubUrl (originalUrl: string, byActor: ActorModel) {
|
||||
return originalUrl + '/announces/' + byActor.id
|
||||
}
|
||||
|
||||
function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
|
||||
|
@ -60,12 +63,13 @@ function getUndoActivityPubUrl (originalUrl: string) {
|
|||
}
|
||||
|
||||
export {
|
||||
getApplicationActivityPubUrl,
|
||||
getVideoActivityPubUrl,
|
||||
getVideoChannelActivityPubUrl,
|
||||
getAccountActivityPubUrl,
|
||||
getVideoAbuseActivityPubUrl,
|
||||
getAccountFollowActivityPubUrl,
|
||||
getAccountFollowAcceptActivityPubUrl,
|
||||
getActorFollowActivityPubUrl,
|
||||
getActorFollowAcceptActivityPubUrl,
|
||||
getAnnounceActivityPubUrl,
|
||||
getUpdateActivityPubUrl,
|
||||
getUndoActivityPubUrl,
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import { VideoChannelObject } from '../../../shared/models/activitypub/objects'
|
||||
import { doRequest, logger } from '../../helpers'
|
||||
import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub'
|
||||
import { ACTIVITY_PUB } from '../../initializers'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
import { videoChannelActivityObjectToDBAttributes } from './process/misc'
|
||||
|
||||
async function getOrCreateVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
|
||||
let videoChannel = await VideoChannelModel.loadByUrl(videoChannelUrl)
|
||||
|
||||
// We don't have this account in our database, fetch it on remote
|
||||
if (!videoChannel) {
|
||||
videoChannel = await fetchRemoteVideoChannel(ownerAccount, videoChannelUrl)
|
||||
if (videoChannel === undefined) throw new Error('Cannot fetch remote video channel.')
|
||||
|
||||
// Save our new video channel in database
|
||||
await videoChannel.save()
|
||||
}
|
||||
|
||||
return videoChannel
|
||||
}
|
||||
|
||||
async function fetchRemoteVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
|
||||
const options = {
|
||||
uri: videoChannelUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': ACTIVITY_PUB.ACCEPT_HEADER
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Fetching remote video channel %s.', videoChannelUrl)
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote video channel %s.', videoChannelUrl, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const videoChannelJSON: VideoChannelObject = JSON.parse(requestResult.body)
|
||||
if (isVideoChannelObjectValid(videoChannelJSON) === false) {
|
||||
logger.debug('Remote video channel JSON is not valid.', { videoChannelJSON })
|
||||
return undefined
|
||||
}
|
||||
|
||||
const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount)
|
||||
const videoChannel = new VideoChannelModel(videoChannelAttributes)
|
||||
videoChannel.Account = ownerAccount
|
||||
|
||||
return videoChannel
|
||||
}
|
||||
|
||||
export {
|
||||
getOrCreateVideoChannel,
|
||||
fetchRemoteVideoChannel
|
||||
}
|
|
@ -19,7 +19,7 @@ import {
|
|||
|
||||
function fetchRemoteVideoPreview (video: VideoModel) {
|
||||
// FIXME: use url
|
||||
const host = video.VideoChannel.Account.Server.host
|
||||
const host = video.VideoChannel.Account.Actor.Server.host
|
||||
const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
|
||||
|
||||
return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
|
||||
|
@ -27,7 +27,7 @@ function fetchRemoteVideoPreview (video: VideoModel) {
|
|||
|
||||
async function fetchRemoteVideoDescription (video: VideoModel) {
|
||||
// FIXME: use url
|
||||
const host = video.VideoChannel.Account.Server.host
|
||||
const host = video.VideoChannel.Account.Actor.Server.host
|
||||
const path = video.getDescriptionPath()
|
||||
const options = {
|
||||
uri: REMOTE_SCHEME.HTTP + '://' + host + path,
|
||||
|
@ -50,43 +50,47 @@ function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject)
|
|||
}
|
||||
|
||||
async function sendVideoRateChangeToFollowers (
|
||||
account: AccountModel,
|
||||
account: AccountModel,
|
||||
video: VideoModel,
|
||||
likes: number,
|
||||
dislikes: number,
|
||||
t: Transaction
|
||||
) {
|
||||
const actor = account.Actor
|
||||
|
||||
// Keep the order: first we undo and then we create
|
||||
|
||||
// Undo Like
|
||||
if (likes < 0) await sendUndoLikeToVideoFollowers(account, video, t)
|
||||
if (likes < 0) await sendUndoLikeToVideoFollowers(actor, video, t)
|
||||
// Undo Dislike
|
||||
if (dislikes < 0) await sendUndoDislikeToVideoFollowers(account, video, t)
|
||||
if (dislikes < 0) await sendUndoDislikeToVideoFollowers(actor, video, t)
|
||||
|
||||
// Like
|
||||
if (likes > 0) await sendLikeToVideoFollowers(account, video, t)
|
||||
if (likes > 0) await sendLikeToVideoFollowers(actor, video, t)
|
||||
// Dislike
|
||||
if (dislikes > 0) await sendCreateDislikeToVideoFollowers(account, video, t)
|
||||
if (dislikes > 0) await sendCreateDislikeToVideoFollowers(actor, video, t)
|
||||
}
|
||||
|
||||
async function sendVideoRateChangeToOrigin (
|
||||
account: AccountModel,
|
||||
account: AccountModel,
|
||||
video: VideoModel,
|
||||
likes: number,
|
||||
dislikes: number,
|
||||
t: Transaction
|
||||
) {
|
||||
const actor = account.Actor
|
||||
|
||||
// Keep the order: first we undo and then we create
|
||||
|
||||
// Undo Like
|
||||
if (likes < 0) await sendUndoLikeToOrigin(account, video, t)
|
||||
if (likes < 0) await sendUndoLikeToOrigin(actor, video, t)
|
||||
// Undo Dislike
|
||||
if (dislikes < 0) await sendUndoDislikeToOrigin(account, video, t)
|
||||
if (dislikes < 0) await sendUndoDislikeToOrigin(actor, video, t)
|
||||
|
||||
// Like
|
||||
if (likes > 0) await sendLikeToOrigin(account, video, t)
|
||||
if (likes > 0) await sendLikeToOrigin(actor, video, t)
|
||||
// Dislike
|
||||
if (dislikes > 0) await sendCreateDislikeToOrigin(account, video, t)
|
||||
if (dislikes > 0) await sendCreateDislikeToOrigin(actor, video, t)
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export * from './activitypub'
|
||||
export * from './cache'
|
||||
export * from './jobs'
|
||||
export * from './oauth-model'
|
||||
export * from './user'
|
||||
export * from './video-channel'
|
|
@ -1,7 +1,7 @@
|
|||
import { JobCategory } from '../../../../shared'
|
||||
import { buildSignedActivity, logger } from '../../../helpers'
|
||||
import { ACTIVITY_PUB } from '../../../initializers'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { JobHandler, JobScheduler } from '../job-scheduler'
|
||||
|
||||
import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
|
||||
|
@ -10,7 +10,7 @@ import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handl
|
|||
|
||||
type ActivityPubHttpPayload = {
|
||||
uris: string[]
|
||||
signatureAccountId?: number
|
||||
signatureActorId?: number
|
||||
body?: any
|
||||
attemptNumber?: number
|
||||
}
|
||||
|
@ -44,10 +44,10 @@ function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, ur
|
|||
async function computeBody (payload: ActivityPubHttpPayload) {
|
||||
let body = payload.body
|
||||
|
||||
if (payload.signatureAccountId) {
|
||||
const accountSignature = await AccountModel.load(payload.signatureAccountId)
|
||||
if (!accountSignature) throw new Error('Unknown signature account id.')
|
||||
body = await buildSignedActivity(accountSignature, payload.body)
|
||||
if (payload.signatureActorId) {
|
||||
const actorSignature = await ActorModel.load(payload.signatureActorId)
|
||||
if (!actorSignature) throw new Error('Unknown signature account id.')
|
||||
body = await buildSignedActivity(actorSignature, payload.body)
|
||||
}
|
||||
|
||||
return body
|
||||
|
|
|
@ -3,7 +3,7 @@ import { computeResolutionsToTranscode, logger } from '../../../helpers'
|
|||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { shareVideoByServer } from '../../activitypub'
|
||||
import { sendAddVideo } from '../../activitypub/send'
|
||||
import { sendCreateVideo } from '../../activitypub/send'
|
||||
import { JobScheduler } from '../job-scheduler'
|
||||
import { TranscodingJobPayload } from './transcoding-job-scheduler'
|
||||
|
||||
|
@ -36,7 +36,8 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
|
|||
if (!videoDatabase) return undefined
|
||||
|
||||
// Now we'll add the video's meta data to our followers
|
||||
await sendAddVideo(video, undefined)
|
||||
await sendCreateVideo(video, undefined)
|
||||
// TODO: share by channel
|
||||
await shareVideoByServer(video, undefined)
|
||||
|
||||
const originalFileHeight = await videoDatabase.getOriginalFileHeight()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { createPrivateAndPublicKeys, logger } from '../helpers'
|
||||
import { CONFIG, sequelizeTypescript } from '../initializers'
|
||||
import { ActivityPubActorType } from '../../shared/models/activitypub'
|
||||
import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../initializers'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { getAccountActivityPubUrl } from './activitypub'
|
||||
import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
|
||||
import { createVideoChannel } from './video-channel'
|
||||
|
||||
async function createUserAccountAndChannel (user: UserModel, validateUser = true) {
|
||||
|
@ -26,31 +25,22 @@ async function createUserAccountAndChannel (user: UserModel, validateUser = true
|
|||
return { account: accountCreated, videoChannel }
|
||||
})
|
||||
|
||||
// Set account keys, this could be long so process after the account creation and do not block the client
|
||||
const { publicKey, privateKey } = await createPrivateAndPublicKeys()
|
||||
const actor = account.Actor
|
||||
actor.set('publicKey', publicKey)
|
||||
actor.set('privateKey', privateKey)
|
||||
actor.save().catch(err => logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err))
|
||||
account.Actor = await setAsyncActorKeys(account.Actor)
|
||||
videoChannel.Actor = await setAsyncActorKeys(videoChannel.Actor)
|
||||
|
||||
return { account, videoChannel }
|
||||
}
|
||||
|
||||
async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) {
|
||||
async function createLocalAccountWithoutKeys (
|
||||
name: string,
|
||||
userId: number,
|
||||
applicationId: number,
|
||||
t: Sequelize.Transaction,
|
||||
type: ActivityPubActorType= 'Person'
|
||||
) {
|
||||
const url = getAccountActivityPubUrl(name)
|
||||
|
||||
const actorInstance = new ActorModel({
|
||||
url,
|
||||
publicKey: null,
|
||||
privateKey: null,
|
||||
followersCount: 0,
|
||||
followingCount: 0,
|
||||
inboxUrl: url + '/inbox',
|
||||
outboxUrl: url + '/outbox',
|
||||
sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
|
||||
followersUrl: url + '/followers',
|
||||
followingUrl: url + '/following'
|
||||
})
|
||||
const actorInstance = buildActorInstance(type, url, name)
|
||||
const actorInstanceCreated = await actorInstance.save({ transaction: t })
|
||||
|
||||
const accountInstance = new AccountModel({
|
||||
|
@ -67,9 +57,18 @@ async function createLocalAccountWithoutKeys (name: string, userId: number, appl
|
|||
return accountInstanceCreated
|
||||
}
|
||||
|
||||
async function createApplicationActor (applicationId: number) {
|
||||
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationId, undefined, 'Application')
|
||||
|
||||
accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor)
|
||||
|
||||
return accountCreated
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
createApplicationActor,
|
||||
createUserAccountAndChannel,
|
||||
createLocalAccountWithoutKeys
|
||||
}
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as uuidv4 from 'uuid/v4'
|
||||
import { VideoChannelCreate } from '../../shared/models'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { VideoChannelModel } from '../models/video/video-channel'
|
||||
import { getVideoChannelActivityPubUrl } from './activitypub'
|
||||
import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub'
|
||||
|
||||
async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
|
||||
const uuid = uuidv4()
|
||||
const url = getVideoChannelActivityPubUrl(uuid)
|
||||
// We use the name as uuid
|
||||
const actorInstance = buildActorInstance('Group', url, uuid, uuid)
|
||||
|
||||
const actorInstanceCreated = await actorInstance.save({ transaction: t })
|
||||
|
||||
const videoChannelData = {
|
||||
name: videoChannelInfo.name,
|
||||
description: videoChannelInfo.description,
|
||||
remote: false,
|
||||
accountId: account.id
|
||||
accountId: account.id,
|
||||
actorId: actorInstanceCreated.id
|
||||
}
|
||||
|
||||
const videoChannel = VideoChannelModel.build(videoChannelData)
|
||||
videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel))
|
||||
|
||||
const options = { transaction: t }
|
||||
|
||||
const videoChannelCreated = await videoChannel.save(options)
|
||||
|
||||
// Do not forget to add Account information to the created video channel
|
||||
// Do not forget to add Account/Actor information to the created video channel
|
||||
videoChannelCreated.Account = account
|
||||
videoChannelCreated.Actor = actorInstanceCreated
|
||||
|
||||
// No need to seed this empty video channel to followers
|
||||
return videoChannelCreated
|
||||
|
|
|
@ -3,33 +3,27 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'
|
|||
import { ActivityPubSignature } from '../../shared'
|
||||
import { isSignatureVerified, logger } from '../helpers'
|
||||
import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers'
|
||||
import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { getOrCreateActorAndServerAndModel } from '../lib/activitypub'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
|
||||
async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
||||
const signatureObject: ActivityPubSignature = req.body.signature
|
||||
|
||||
logger.debug('Checking signature of account %s...', signatureObject.creator)
|
||||
logger.debug('Checking signature of actor %s...', signatureObject.creator)
|
||||
|
||||
let account = await AccountModel.loadByUrl(signatureObject.creator)
|
||||
|
||||
// We don't have this account in our database, fetch it on remote
|
||||
if (!account) {
|
||||
account = await fetchRemoteAccount(signatureObject.creator)
|
||||
|
||||
if (!account) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
// Save our new account and its server in database
|
||||
await saveAccountAndServerIfNotExist(account)
|
||||
let actor: ActorModel
|
||||
try {
|
||||
actor = await getOrCreateActorAndServerAndModel(signatureObject.creator)
|
||||
} catch (err) {
|
||||
logger.error('Cannot create remote actor and check signature.', err)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const verified = await isSignatureVerified(account, req.body)
|
||||
const verified = await isSignatureVerified(actor, req.body)
|
||||
if (verified === false) return res.sendStatus(403)
|
||||
|
||||
res.locals.signature = {
|
||||
account
|
||||
actor
|
||||
}
|
||||
|
||||
return next()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as express from 'express'
|
||||
import { body, param } from 'express-validator/check'
|
||||
import { getServerAccount, isTestInstance, logger } from '../../helpers'
|
||||
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||
import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
|
||||
import { getServerActor, isTestInstance, logger } from '../../helpers'
|
||||
import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers'
|
||||
import { CONFIG } from '../../initializers'
|
||||
import { AccountFollowModel } from '../../models/account/account-follow'
|
||||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
||||
const followValidator = [
|
||||
|
@ -29,15 +28,15 @@ const followValidator = [
|
|||
]
|
||||
|
||||
const removeFollowingValidator = [
|
||||
param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
|
||||
param('host').custom(isHostValid).withMessage('Should have a valid host'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking unfollow parameters', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
const serverAccount = await getServerAccount()
|
||||
const follow = await AccountFollowModel.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
|
||||
const serverActor = await getServerActor()
|
||||
const follow = await ActorFollowModel.loadByActorAndTargetHost(serverActor.id, req.params.host)
|
||||
|
||||
if (!follow) {
|
||||
return res.status(404)
|
||||
|
|
|
@ -3,7 +3,7 @@ import { body, param } from 'express-validator/check'
|
|||
import { UserRight } from '../../../shared'
|
||||
import { logger } from '../../helpers'
|
||||
import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
|
||||
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
|
||||
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||
import {
|
||||
isVideoChannelDescriptionValid,
|
||||
isVideoChannelExist,
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
} from '../../helpers/custom-validators/video-channels'
|
||||
import { UserModel } from '../../models/account/user'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
import { VideoChannelShareModel } from '../../models/video/video-channel-share'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
||||
const listVideoAccountChannelsValidator = [
|
||||
|
@ -98,28 +97,6 @@ const videoChannelsGetValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videoChannelsShareValidator = [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoChannelShare parameters', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isVideoChannelExist(req.params.id, res)) return
|
||||
|
||||
const share = await VideoChannelShareModel.load(res.locals.video.id, req.params.accountId, undefined)
|
||||
if (!share) {
|
||||
return res.status(404)
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.videoChannelShare = share
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -127,15 +104,13 @@ export {
|
|||
videoChannelsAddValidator,
|
||||
videoChannelsUpdateValidator,
|
||||
videoChannelsRemoveValidator,
|
||||
videoChannelsGetValidator,
|
||||
videoChannelsShareValidator
|
||||
videoChannelsGetValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
|
||||
// Retrieve the user who did the request
|
||||
if (videoChannel.isOwned() === false) {
|
||||
if (videoChannel.Actor.isOwned() === false) {
|
||||
res.status(403)
|
||||
.json({ error: 'Cannot remove video channel of another server.' })
|
||||
.end()
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
|||
import { query } from 'express-validator/check'
|
||||
import { logger } from '../../helpers'
|
||||
import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
||||
const webfingerValidator = [
|
||||
|
@ -17,14 +17,14 @@ const webfingerValidator = [
|
|||
const nameWithHost = req.query.resource.substr(5)
|
||||
const [ name ] = nameWithHost.split('@')
|
||||
|
||||
const account = await AccountModel.loadLocalByName(name)
|
||||
if (!account) {
|
||||
const actor = await ActorModel.loadLocalByName(name)
|
||||
if (!actor) {
|
||||
return res.status(404)
|
||||
.send({ error: 'Account not found' })
|
||||
.send({ error: 'Actor not found' })
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.account = account
|
||||
res.locals.actor = actor
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { values } from 'lodash'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { FollowState } from '../../../shared/models/accounts'
|
||||
import { FOLLOW_STATES } from '../../initializers/constants'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort } from '../utils'
|
||||
import { AccountModel } from './account'
|
||||
|
||||
@Table({
|
||||
tableName: 'accountFollow',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'accountId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'targetAccountId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'accountId', 'targetAccountId' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class AccountFollowModel extends Model<AccountFollowModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Column(DataType.ENUM(values(FOLLOW_STATES)))
|
||||
state: FollowState
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => AccountModel)
|
||||
@Column
|
||||
accountId: number
|
||||
|
||||
@BelongsTo(() => AccountModel, {
|
||||
foreignKey: {
|
||||
name: 'accountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'AccountFollower',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
AccountFollower: AccountModel
|
||||
|
||||
@ForeignKey(() => AccountModel)
|
||||
@Column
|
||||
targetAccountId: number
|
||||
|
||||
@BelongsTo(() => AccountModel, {
|
||||
foreignKey: {
|
||||
name: 'targetAccountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'AccountFollowing',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
AccountFollowing: AccountModel
|
||||
|
||||
static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
accountId,
|
||||
targetAccountId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
required: true,
|
||||
as: 'AccountFollower'
|
||||
},
|
||||
{
|
||||
model: AccountModel,
|
||||
required: true,
|
||||
as: 'AccountFollowing'
|
||||
}
|
||||
],
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return AccountFollowModel.findOne(query)
|
||||
}
|
||||
|
||||
static listFollowingForApi (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
required: true,
|
||||
as: 'AccountFollower',
|
||||
where: {
|
||||
id
|
||||
}
|
||||
},
|
||||
{
|
||||
model: AccountModel,
|
||||
as: 'AccountFollowing',
|
||||
required: true,
|
||||
include: [ ServerModel ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountFollowModel.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static listFollowersForApi (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
required: true,
|
||||
as: 'AccountFollower',
|
||||
include: [ ServerModel ]
|
||||
},
|
||||
{
|
||||
model: AccountModel,
|
||||
as: 'AccountFollowing',
|
||||
required: true,
|
||||
where: {
|
||||
id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountFollowModel.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
|
||||
return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
|
||||
}
|
||||
|
||||
static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) {
|
||||
return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
|
||||
}
|
||||
|
||||
static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
|
||||
return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
|
||||
}
|
||||
|
||||
private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
|
||||
accountIds: number[],
|
||||
t: Sequelize.Transaction,
|
||||
start?: number,
|
||||
count?: number,
|
||||
columnUrl = 'url') {
|
||||
let firstJoin: string
|
||||
let secondJoin: string
|
||||
|
||||
if (type === 'followers') {
|
||||
firstJoin = 'targetAccountId'
|
||||
secondJoin = 'accountId'
|
||||
} else {
|
||||
firstJoin = 'accountId'
|
||||
secondJoin = 'targetAccountId'
|
||||
}
|
||||
|
||||
const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
|
||||
const tasks: Bluebird<any>[] = []
|
||||
|
||||
for (const selection of selections) {
|
||||
let query = 'SELECT ' + selection + ' FROM "account" ' +
|
||||
'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' +
|
||||
'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' +
|
||||
'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' '
|
||||
|
||||
if (count !== undefined) query += 'LIMIT ' + count
|
||||
if (start !== undefined) query += ' OFFSET ' + start
|
||||
|
||||
const options = {
|
||||
bind: { accountIds },
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
transaction: t
|
||||
}
|
||||
tasks.push(AccountFollowModel.sequelize.query(query, options))
|
||||
}
|
||||
|
||||
const [ followers, [ { total } ] ] = await
|
||||
Promise.all(tasks)
|
||||
const urls: string[] = followers.map(f => f.url)
|
||||
|
||||
return {
|
||||
data: urls,
|
||||
total: parseInt(total, 10)
|
||||
}
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
const follower = this.AccountFollower.toFormattedJSON()
|
||||
const following = this.AccountFollowing.toFormattedJSON()
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
follower,
|
||||
following,
|
||||
state: this.state,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,18 +5,16 @@ import {
|
|||
BelongsTo,
|
||||
Column,
|
||||
CreatedAt,
|
||||
DataType,
|
||||
Default,
|
||||
DefaultScope,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Is,
|
||||
IsUUID,
|
||||
Model,
|
||||
Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { isUserUsernameValid } from '../../helpers/custom-validators/users'
|
||||
import { sendDeleteAccount } from '../../lib/activitypub/send'
|
||||
import { sendDeleteActor } from '../../lib/activitypub/send'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { ApplicationModel } from '../application/application'
|
||||
import { ServerModel } from '../server/server'
|
||||
|
@ -24,31 +22,30 @@ import { throwIfNotValid } from '../utils'
|
|||
import { VideoChannelModel } from '../video/video-channel'
|
||||
import { UserModel } from './user'
|
||||
|
||||
@Table({
|
||||
tableName: 'account',
|
||||
indexes: [
|
||||
@DefaultScope({
|
||||
include: [
|
||||
{
|
||||
fields: [ 'name' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'serverId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'userId' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'applicationId' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'name', 'serverId', 'applicationId' ],
|
||||
unique: true
|
||||
model: () => ActorModel,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: () => ServerModel,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
@Table({
|
||||
tableName: 'account'
|
||||
})
|
||||
export class AccountModel extends Model<AccountModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
|
||||
@Column
|
||||
name: string
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
|
@ -89,7 +86,7 @@ export class AccountModel extends Model<AccountModel> {
|
|||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Application: ApplicationModel
|
||||
Account: ApplicationModel
|
||||
|
||||
@HasMany(() => VideoChannelModel, {
|
||||
foreignKey: {
|
||||
|
@ -103,32 +100,27 @@ export class AccountModel extends Model<AccountModel> {
|
|||
@AfterDestroy
|
||||
static sendDeleteIfOwned (instance: AccountModel) {
|
||||
if (instance.isOwned()) {
|
||||
return sendDeleteAccount(instance, undefined)
|
||||
return sendDeleteActor(instance.Actor, undefined)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
static loadApplication () {
|
||||
return AccountModel.findOne({
|
||||
include: [
|
||||
{
|
||||
model: ApplicationModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static load (id: number) {
|
||||
return AccountModel.findById(id)
|
||||
}
|
||||
|
||||
static loadByUUID (uuid: string) {
|
||||
const query = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountModel.findOne(query)
|
||||
|
@ -156,25 +148,6 @@ export class AccountModel extends Model<AccountModel> {
|
|||
return AccountModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByNameAndHost (name: string, host: string) {
|
||||
const query = {
|
||||
where: {
|
||||
name
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ServerModel,
|
||||
required: true,
|
||||
where: {
|
||||
host
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
include: [
|
||||
|
@ -192,29 +165,11 @@ export class AccountModel extends Model<AccountModel> {
|
|||
return AccountModel.findOne(query)
|
||||
}
|
||||
|
||||
static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
followersUrl: {
|
||||
[ Sequelize.Op.in ]: followersUrls
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
transaction
|
||||
}
|
||||
|
||||
return AccountModel.findAll(query)
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
const actor = this.Actor.toFormattedJSON()
|
||||
const account = {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
|
@ -223,7 +178,7 @@ export class AccountModel extends Model<AccountModel> {
|
|||
}
|
||||
|
||||
toActivityPubObject () {
|
||||
return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account')
|
||||
return this.Actor.toActivityPubObject(this.name, 'Account')
|
||||
}
|
||||
|
||||
isOwned () {
|
||||
|
|
|
@ -1,26 +1,13 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import {
|
||||
AllowNull,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
Column, CreatedAt,
|
||||
DataType,
|
||||
Default, DefaultScope,
|
||||
HasMany,
|
||||
HasOne,
|
||||
Is,
|
||||
IsEmail,
|
||||
Model, Scopes,
|
||||
Table, UpdatedAt
|
||||
AllowNull, BeforeCreate, BeforeUpdate, Column, CreatedAt, DataType, Default, DefaultScope, HasMany, HasOne, Is, IsEmail, Model,
|
||||
Scopes, Table, UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
|
||||
import { comparePassword, cryptPassword } from '../../helpers'
|
||||
import {
|
||||
comparePassword,
|
||||
cryptPassword
|
||||
} from '../../helpers'
|
||||
import {
|
||||
isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
|
||||
isUserVideoQuotaValid, isUserAutoPlayVideoValid
|
||||
isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
|
||||
isUserVideoQuotaValid
|
||||
} from '../../helpers/custom-validators/users'
|
||||
import { OAuthTokenModel } from '../oauth/oauth-token'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { values } from 'lodash'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { FollowState } from '../../../shared/models/actors'
|
||||
import { FOLLOW_STATES } from '../../initializers/constants'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort } from '../utils'
|
||||
import { ActorModel } from './actor'
|
||||
|
||||
@Table({
|
||||
tableName: 'actorFollow',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'actorId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'targetActorId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'actorId', 'targetActorId' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ActorFollowModel extends Model<ActorFollowModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Column(DataType.ENUM(values(FOLLOW_STATES)))
|
||||
state: FollowState
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => ActorModel)
|
||||
@Column
|
||||
actorId: number
|
||||
|
||||
@BelongsTo(() => ActorModel, {
|
||||
foreignKey: {
|
||||
name: 'actorId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'ActorFollower',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
ActorFollower: ActorModel
|
||||
|
||||
@ForeignKey(() => ActorModel)
|
||||
@Column
|
||||
targetActorId: number
|
||||
|
||||
@BelongsTo(() => ActorModel, {
|
||||
foreignKey: {
|
||||
name: 'targetActorId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'ActorFollowing',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
ActorFollowing: ActorModel
|
||||
|
||||
static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
actorId,
|
||||
targetActorId: targetActorId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollower'
|
||||
},
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollowing'
|
||||
}
|
||||
],
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return ActorFollowModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
actorId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollower'
|
||||
},
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollowing',
|
||||
include: [
|
||||
{
|
||||
model: ServerModel,
|
||||
required: true,
|
||||
where: {
|
||||
host: targetHost
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return ActorFollowModel.findOne(query)
|
||||
}
|
||||
|
||||
static listFollowingForApi (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollower',
|
||||
where: {
|
||||
id
|
||||
}
|
||||
},
|
||||
{
|
||||
model: ActorModel,
|
||||
as: 'ActorFollowing',
|
||||
required: true,
|
||||
include: [ ServerModel ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return ActorFollowModel.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static listFollowersForApi (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
as: 'ActorFollower',
|
||||
include: [ ServerModel ]
|
||||
},
|
||||
{
|
||||
model: ActorModel,
|
||||
as: 'ActorFollowing',
|
||||
required: true,
|
||||
where: {
|
||||
id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return ActorFollowModel.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
|
||||
return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count)
|
||||
}
|
||||
|
||||
static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) {
|
||||
return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, undefined, undefined, 'sharedInboxUrl')
|
||||
}
|
||||
|
||||
static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
|
||||
return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count)
|
||||
}
|
||||
|
||||
private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
|
||||
actorIds: number[],
|
||||
t: Sequelize.Transaction,
|
||||
start?: number,
|
||||
count?: number,
|
||||
columnUrl = 'url') {
|
||||
let firstJoin: string
|
||||
let secondJoin: string
|
||||
|
||||
if (type === 'followers') {
|
||||
firstJoin = 'targetActorId'
|
||||
secondJoin = 'actorId'
|
||||
} else {
|
||||
firstJoin = 'actorId'
|
||||
secondJoin = 'targetActorId'
|
||||
}
|
||||
|
||||
const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
|
||||
const tasks: Bluebird<any>[] = []
|
||||
|
||||
for (const selection of selections) {
|
||||
let query = 'SELECT ' + selection + ' FROM "actor" ' +
|
||||
'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
|
||||
'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
|
||||
'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' '
|
||||
|
||||
if (count !== undefined) query += 'LIMIT ' + count
|
||||
if (start !== undefined) query += ' OFFSET ' + start
|
||||
|
||||
const options = {
|
||||
bind: { actorIds },
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
transaction: t
|
||||
}
|
||||
tasks.push(ActorFollowModel.sequelize.query(query, options))
|
||||
}
|
||||
|
||||
const [ followers, [ { total } ] ] = await
|
||||
Promise.all(tasks)
|
||||
const urls: string[] = followers.map(f => f.url)
|
||||
|
||||
return {
|
||||
data: urls,
|
||||
total: parseInt(total, 10)
|
||||
}
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
const follower = this.ActorFollower.toFormattedJSON()
|
||||
const following = this.ActorFollowing.toFormattedJSON()
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
follower,
|
||||
following,
|
||||
state: this.state,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +1,75 @@
|
|||
import { values } from 'lodash'
|
||||
import { join } from 'path'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import {
|
||||
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table,
|
||||
AllowNull,
|
||||
BelongsTo,
|
||||
Column,
|
||||
CreatedAt,
|
||||
DataType,
|
||||
Default,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
HasOne,
|
||||
Is,
|
||||
IsUUID,
|
||||
Model,
|
||||
Scopes,
|
||||
Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { ActivityPubActorType } from '../../../shared/models/activitypub'
|
||||
import { Avatar } from '../../../shared/models/avatars/avatar.model'
|
||||
import { activityPubContextify } from '../../helpers'
|
||||
import {
|
||||
isActivityPubUrlValid,
|
||||
isActorFollowersCountValid,
|
||||
isActorFollowingCountValid, isActorPreferredUsernameValid,
|
||||
isActorFollowingCountValid,
|
||||
isActorNameValid,
|
||||
isActorPrivateKeyValid,
|
||||
isActorPublicKeyValid
|
||||
} from '../../helpers/custom-validators/activitypub'
|
||||
import { isUserUsernameValid } from '../../helpers/custom-validators/users'
|
||||
import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { AccountFollowModel } from '../account/account-follow'
|
||||
import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { AvatarModel } from '../avatar/avatar'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { throwIfNotValid } from '../utils'
|
||||
import { VideoChannelModel } from '../video/video-channel'
|
||||
import { ActorFollowModel } from './actor-follow'
|
||||
|
||||
enum ScopeNames {
|
||||
FULL = 'FULL'
|
||||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.FULL]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: () => VideoChannelModel,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
@Table({
|
||||
tableName: 'actor'
|
||||
tableName: 'actor',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'name', 'serverId' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ActorModel extends Model<ActorModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Column(DataType.ENUM(values(ACTIVITY_PUB_ACTOR_TYPES)))
|
||||
type: ActivityPubActorType
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(DataType.UUIDV4)
|
||||
@IsUUID(4)
|
||||
|
@ -32,7 +77,7 @@ export class ActorModel extends Model<ActorModel> {
|
|||
uuid: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name'))
|
||||
@Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name'))
|
||||
@Column
|
||||
name: string
|
||||
|
||||
|
@ -104,24 +149,24 @@ export class ActorModel extends Model<ActorModel> {
|
|||
})
|
||||
Avatar: AvatarModel
|
||||
|
||||
@HasMany(() => AccountFollowModel, {
|
||||
@HasMany(() => ActorFollowModel, {
|
||||
foreignKey: {
|
||||
name: 'accountId',
|
||||
name: 'actorId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
AccountFollowing: AccountFollowModel[]
|
||||
AccountFollowing: ActorFollowModel[]
|
||||
|
||||
@HasMany(() => AccountFollowModel, {
|
||||
@HasMany(() => ActorFollowModel, {
|
||||
foreignKey: {
|
||||
name: 'targetAccountId',
|
||||
name: 'targetActorId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'followers',
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
AccountFollowers: AccountFollowModel[]
|
||||
AccountFollowers: ActorFollowModel[]
|
||||
|
||||
@ForeignKey(() => ServerModel)
|
||||
@Column
|
||||
|
@ -135,6 +180,36 @@ export class ActorModel extends Model<ActorModel> {
|
|||
})
|
||||
Server: ServerModel
|
||||
|
||||
@HasOne(() => AccountModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Account: AccountModel
|
||||
|
||||
@HasOne(() => VideoChannelModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
VideoChannel: VideoChannelModel
|
||||
|
||||
static load (id: number) {
|
||||
return ActorModel.scope(ScopeNames.FULL).findById(id)
|
||||
}
|
||||
|
||||
static loadByUUID (uuid: string) {
|
||||
const query = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
}
|
||||
|
||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
||||
}
|
||||
|
||||
static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
|
@ -145,7 +220,48 @@ export class ActorModel extends Model<ActorModel> {
|
|||
transaction
|
||||
}
|
||||
|
||||
return ActorModel.findAll(query)
|
||||
return ActorModel.scope(ScopeNames.FULL).findAll(query)
|
||||
}
|
||||
|
||||
static loadLocalByName (name: string) {
|
||||
const query = {
|
||||
where: {
|
||||
name,
|
||||
serverId: null
|
||||
}
|
||||
}
|
||||
|
||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
||||
}
|
||||
|
||||
static loadByNameAndHost (name: string, host: string) {
|
||||
const query = {
|
||||
where: {
|
||||
name
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ServerModel,
|
||||
required: true,
|
||||
where: {
|
||||
host
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
||||
}
|
||||
|
||||
static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
url
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return ActorModel.scope(ScopeNames.FULL).findOne(query)
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
|
@ -167,6 +283,7 @@ export class ActorModel extends Model<ActorModel> {
|
|||
|
||||
return {
|
||||
id: this.id,
|
||||
uuid: this.uuid,
|
||||
host,
|
||||
score,
|
||||
followingCount: this.followingCount,
|
||||
|
@ -175,28 +292,30 @@ export class ActorModel extends Model<ActorModel> {
|
|||
}
|
||||
}
|
||||
|
||||
toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') {
|
||||
toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') {
|
||||
let activityPubType
|
||||
if (type === 'Account') {
|
||||
activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
|
||||
activityPubType = 'Person' as 'Person'
|
||||
} else if (type === 'Application') {
|
||||
activityPubType = 'Application' as 'Application'
|
||||
} else { // VideoChannel
|
||||
activityPubType = 'Group'
|
||||
activityPubType = 'Group' as 'Group'
|
||||
}
|
||||
|
||||
const json = {
|
||||
type,
|
||||
type: activityPubType,
|
||||
id: this.url,
|
||||
following: this.getFollowingUrl(),
|
||||
followers: this.getFollowersUrl(),
|
||||
inbox: this.inboxUrl,
|
||||
outbox: this.outboxUrl,
|
||||
preferredUsername: name,
|
||||
preferredUsername,
|
||||
url: this.url,
|
||||
name,
|
||||
name: this.name,
|
||||
endpoints: {
|
||||
sharedInbox: this.sharedInboxUrl
|
||||
},
|
||||
uuid,
|
||||
uuid: this.uuid,
|
||||
publicKey: {
|
||||
id: this.getPublicKeyUrl(),
|
||||
owner: this.url,
|
||||
|
@ -212,11 +331,11 @@ export class ActorModel extends Model<ActorModel> {
|
|||
attributes: [ 'sharedInboxUrl' ],
|
||||
include: [
|
||||
{
|
||||
model: AccountFollowModel,
|
||||
model: ActorFollowModel,
|
||||
required: true,
|
||||
as: 'followers',
|
||||
where: {
|
||||
targetAccountId: this.id
|
||||
targetActorId: this.id
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript'
|
||||
import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
|
||||
import { AccountModel } from '../account/account'
|
||||
|
||||
@DefaultScope({
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
})
|
||||
@Table({
|
||||
tableName: 'application'
|
||||
})
|
||||
|
@ -11,7 +20,19 @@ export class ApplicationModel extends Model<ApplicationModel> {
|
|||
@Column
|
||||
migrationVersion: number
|
||||
|
||||
@HasOne(() => AccountModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Account: AccountModel
|
||||
|
||||
static countTotal () {
|
||||
return ApplicationModel.count()
|
||||
}
|
||||
|
||||
static load () {
|
||||
return ApplicationModel.findOne()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
|
|||
import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos'
|
||||
import { CONFIG } from '../../initializers'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { VideoModel } from './video'
|
||||
|
||||
|
@ -63,13 +62,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
|
|||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: ServerModel,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
required: true
|
||||
},
|
||||
{
|
||||
model: VideoModel,
|
||||
|
@ -87,8 +80,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
|
|||
toFormattedJSON () {
|
||||
let reporterServerHost
|
||||
|
||||
if (this.Account.Server) {
|
||||
reporterServerHost = this.Account.Server.host
|
||||
if (this.Account.Actor.Server) {
|
||||
reporterServerHost = this.Account.Actor.Server.host
|
||||
} else {
|
||||
// It means it's our video
|
||||
reporterServerHost = CONFIG.WEBSERVER.HOST
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { VideoChannelModel } from './video-channel'
|
||||
|
||||
enum ScopeNames {
|
||||
FULL = 'FULL',
|
||||
WITH_ACCOUNT = 'WITH_ACCOUNT'
|
||||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.FULL]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
model: () => VideoChannelModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
[ScopeNames.WITH_ACCOUNT]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
@Table({
|
||||
tableName: 'videoChannelShare',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'accountId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'videoChannelId' ]
|
||||
}
|
||||
]
|
||||
})
|
||||
export class VideoChannelShareModel extends Model<VideoChannelShareModel> {
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => AccountModel)
|
||||
@Column
|
||||
accountId: number
|
||||
|
||||
@BelongsTo(() => AccountModel, {
|
||||
foreignKey: {
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Account: AccountModel
|
||||
|
||||
@ForeignKey(() => VideoChannelModel)
|
||||
@Column
|
||||
videoChannelId: number
|
||||
|
||||
@BelongsTo(() => VideoChannelModel, {
|
||||
foreignKey: {
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
VideoChannel: VideoChannelModel
|
||||
|
||||
static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
|
||||
return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({
|
||||
where: {
|
||||
accountId,
|
||||
videoChannelId
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
}
|
||||
|
||||
static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
videoChannelId
|
||||
},
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query)
|
||||
.then(res => res.map(r => r.Account))
|
||||
}
|
||||
}
|
|
@ -1,42 +1,52 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import {
|
||||
AfterDestroy,
|
||||
AllowNull,
|
||||
BelongsTo,
|
||||
Column,
|
||||
CreatedAt,
|
||||
DataType,
|
||||
Default,
|
||||
DefaultScope,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Is,
|
||||
IsUUID,
|
||||
Model,
|
||||
Scopes,
|
||||
Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
|
||||
import { ActivityPubActor } from '../../../shared/models/activitypub'
|
||||
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
|
||||
import { sendDeleteVideoChannel } from '../../lib/activitypub/send'
|
||||
import { sendDeleteActor } from '../../lib/activitypub/send'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { VideoModel } from './video'
|
||||
import { VideoChannelShareModel } from './video-channel-share'
|
||||
|
||||
enum ScopeNames {
|
||||
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
||||
WITH_ACTOR = 'WITH_ACTOR',
|
||||
WITH_VIDEOS = 'WITH_VIDEOS'
|
||||
}
|
||||
|
||||
@DefaultScope({
|
||||
include: [
|
||||
{
|
||||
model: () => ActorModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
})
|
||||
@Scopes({
|
||||
[ScopeNames.WITH_ACCOUNT]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
include: [ { model: () => ServerModel, required: false } ]
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: () => ActorModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -44,6 +54,11 @@ enum ScopeNames {
|
|||
include: [
|
||||
() => VideoModel
|
||||
]
|
||||
},
|
||||
[ScopeNames.WITH_ACTOR]: {
|
||||
include: [
|
||||
() => ActorModel
|
||||
]
|
||||
}
|
||||
})
|
||||
@Table({
|
||||
|
@ -56,12 +71,6 @@ enum ScopeNames {
|
|||
})
|
||||
export class VideoChannelModel extends Model<VideoChannelModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(DataType.UUIDV4)
|
||||
@IsUUID(4)
|
||||
@Column(DataType.UUID)
|
||||
uuid: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
|
||||
@Column
|
||||
|
@ -72,10 +81,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
@Column
|
||||
description: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
remote: boolean
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
|
@ -115,19 +120,10 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
})
|
||||
Videos: VideoModel[]
|
||||
|
||||
@HasMany(() => VideoChannelShareModel, {
|
||||
foreignKey: {
|
||||
name: 'channelId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
VideoChannelShares: VideoChannelShareModel[]
|
||||
|
||||
@AfterDestroy
|
||||
static sendDeleteIfOwned (instance: VideoChannelModel) {
|
||||
if (instance.isOwned()) {
|
||||
return sendDeleteVideoChannel(instance, undefined)
|
||||
if (instance.Actor.isOwned()) {
|
||||
return sendDeleteActor(instance.Actor, undefined)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
@ -150,7 +146,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
order: [ getSort(sort) ]
|
||||
}
|
||||
|
||||
return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query)
|
||||
return VideoChannelModel
|
||||
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
|
@ -165,51 +163,18 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
where: {
|
||||
id: accountId
|
||||
},
|
||||
required: true,
|
||||
include: [ { model: ServerModel, required: false } ]
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannelModel.findAndCountAll(query)
|
||||
return VideoChannelModel
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
static loadByUrl (url: string, t?: Sequelize.Transaction) {
|
||||
const query: IFindOptions<VideoChannelModel> = {
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
url
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
|
||||
}
|
||||
|
||||
static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) {
|
||||
const query: IFindOptions<VideoChannelModel> = {
|
||||
where: {
|
||||
[ Sequelize.Op.or ]: [
|
||||
{ uuid },
|
||||
{ url }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return VideoChannelModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByIdAndAccount (id: number, accountId: number) {
|
||||
const options = {
|
||||
where: {
|
||||
|
@ -218,21 +183,33 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
}
|
||||
}
|
||||
|
||||
return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
|
||||
return VideoChannelModel
|
||||
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
|
||||
.findOne(options)
|
||||
}
|
||||
|
||||
static loadAndPopulateAccount (id: number) {
|
||||
return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id)
|
||||
return VideoChannelModel
|
||||
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
|
||||
.findById(id)
|
||||
}
|
||||
|
||||
static loadByUUIDAndPopulateAccount (uuid: string) {
|
||||
const options = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
|
||||
return VideoChannelModel
|
||||
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
|
||||
.findOne(options)
|
||||
}
|
||||
|
||||
static loadAndPopulateAccountAndVideos (id: number) {
|
||||
|
@ -242,39 +219,36 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
]
|
||||
}
|
||||
|
||||
return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options)
|
||||
}
|
||||
|
||||
isOwned () {
|
||||
return this.remote === false
|
||||
return VideoChannelModel
|
||||
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
|
||||
.findById(id, options)
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
const json = {
|
||||
const actor = this.Actor.toFormattedJSON()
|
||||
const account = {
|
||||
id: this.id,
|
||||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
isLocal: this.isOwned(),
|
||||
isLocal: this.Actor.isOwned(),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
|
||||
if (this.Account !== undefined) {
|
||||
json[ 'owner' ] = {
|
||||
name: this.Account.name,
|
||||
uuid: this.Account.uuid
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(this.Videos)) {
|
||||
json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON())
|
||||
}
|
||||
|
||||
return json
|
||||
return Object.assign(actor, account)
|
||||
}
|
||||
|
||||
toActivityPubObject () {
|
||||
return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel')
|
||||
toActivityPubObject (): ActivityPubActor {
|
||||
const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
|
||||
|
||||
return Object.assign(obj, {
|
||||
summary: this.description,
|
||||
attributedTo: [
|
||||
{
|
||||
type: 'Person' as 'Person',
|
||||
id: this.Account.Actor.url
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { VideoModel } from './video'
|
||||
|
||||
enum ScopeNames {
|
||||
FULL = 'FULL',
|
||||
WITH_ACCOUNT = 'WITH_ACCOUNT'
|
||||
WITH_ACTOR = 'WITH_ACTOR'
|
||||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.FULL]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
model: () => ActorModel,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
|
@ -21,10 +21,10 @@ enum ScopeNames {
|
|||
}
|
||||
]
|
||||
},
|
||||
[ScopeNames.WITH_ACCOUNT]: {
|
||||
[ScopeNames.WITH_ACTOR]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
model: () => ActorModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
|
@ -34,7 +34,7 @@ enum ScopeNames {
|
|||
tableName: 'videoShare',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'accountId' ]
|
||||
fields: [ 'actorId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'videoId' ]
|
||||
|
@ -48,17 +48,17 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
|||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => AccountModel)
|
||||
@ForeignKey(() => ActorModel)
|
||||
@Column
|
||||
accountId: number
|
||||
actorId: number
|
||||
|
||||
@BelongsTo(() => AccountModel, {
|
||||
@BelongsTo(() => ActorModel, {
|
||||
foreignKey: {
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Account: AccountModel
|
||||
Actor: ActorModel
|
||||
|
||||
@ForeignKey(() => VideoModel)
|
||||
@Column
|
||||
|
@ -72,24 +72,24 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
|||
})
|
||||
Video: VideoModel
|
||||
|
||||
static load (accountId: number, videoId: number, t: Sequelize.Transaction) {
|
||||
return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({
|
||||
static load (actorId: number, videoId: number, t: Sequelize.Transaction) {
|
||||
return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
|
||||
where: {
|
||||
accountId,
|
||||
actorId,
|
||||
videoId
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
}
|
||||
|
||||
static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) {
|
||||
static loadActorsByShare (videoId: number, t: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
videoId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
model: ActorModel,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
|
@ -97,6 +97,6 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
|||
}
|
||||
|
||||
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
|
||||
.then(res => res.map(r => r.Account))
|
||||
.then(res => res.map(r => r.Actor))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,9 +66,10 @@ import {
|
|||
VIDEO_PRIVACIES
|
||||
} from '../../initializers'
|
||||
import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
|
||||
import { sendDeleteVideo } from '../../lib/index'
|
||||
import { sendDeleteVideo } from '../../lib/activitypub/send'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { AccountVideoRateModel } from '../account/account-video-rate'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { TagModel } from './tag'
|
||||
|
@ -79,8 +80,7 @@ import { VideoShareModel } from './video-share'
|
|||
import { VideoTagModel } from './video-tag'
|
||||
|
||||
enum ScopeNames {
|
||||
NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST',
|
||||
PUBLIC = 'PUBLIC',
|
||||
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
||||
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
||||
WITH_TAGS = 'WITH_TAGS',
|
||||
WITH_FILES = 'WITH_FILES',
|
||||
|
@ -89,17 +89,13 @@ enum ScopeNames {
|
|||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.NOT_IN_BLACKLIST]: {
|
||||
[ScopeNames.AVAILABLE_FOR_LIST]: {
|
||||
where: {
|
||||
id: {
|
||||
[Sequelize.Op.notIn]: Sequelize.literal(
|
||||
'(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
[ScopeNames.PUBLIC]: {
|
||||
where: {
|
||||
},
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
},
|
||||
|
@ -114,8 +110,14 @@ enum ScopeNames {
|
|||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: () => ServerModel,
|
||||
required: false
|
||||
model: () => ActorModel,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: () => ServerModel,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -138,7 +140,7 @@ enum ScopeNames {
|
|||
include: [
|
||||
{
|
||||
model: () => VideoShareModel,
|
||||
include: [ () => AccountModel ]
|
||||
include: [ () => ActorModel ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -271,7 +273,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
|
||||
@BelongsTo(() => VideoChannelModel, {
|
||||
foreignKey: {
|
||||
allowNull: false
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
|
@ -351,14 +353,15 @@ export class VideoModel extends Model<VideoModel> {
|
|||
return VideoModel.scope(ScopeNames.WITH_FILES).findAll()
|
||||
}
|
||||
|
||||
static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) {
|
||||
static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) {
|
||||
function getRawQuery (select: string) {
|
||||
const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' +
|
||||
'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
|
||||
'WHERE "VideoChannel"."accountId" = ' + accountId
|
||||
'INNER JOIN "account" AS "Account" ON "Account"."id" = "VideoChannel"."accountId" ' +
|
||||
'WHERE "Account"."actorId" = ' + actorId
|
||||
const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' +
|
||||
'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
|
||||
'WHERE "VideoShare"."accountId" = ' + accountId
|
||||
'WHERE "VideoShare"."actorId" = ' + actorId
|
||||
|
||||
return `(${queryVideo}) UNION (${queryVideoShare})`
|
||||
}
|
||||
|
@ -388,11 +391,16 @@ export class VideoModel extends Model<VideoModel> {
|
|||
}
|
||||
},
|
||||
{
|
||||
accountId
|
||||
actorId
|
||||
}
|
||||
]
|
||||
},
|
||||
include: [ AccountModel ]
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: VideoChannelModel,
|
||||
|
@ -469,7 +477,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
order: [ getSort(sort) ]
|
||||
}
|
||||
|
||||
return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ])
|
||||
return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST, ScopeNames.WITH_ACCOUNT ])
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
|
@ -541,7 +549,13 @@ export class VideoModel extends Model<VideoModel> {
|
|||
|
||||
const accountInclude: IIncludeOptions = {
|
||||
model: AccountModel,
|
||||
include: [ serverInclude ]
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
include: [ serverInclude ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const videoChannelInclude: IIncludeOptions = {
|
||||
|
@ -586,7 +600,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
videoChannelInclude, tagInclude
|
||||
]
|
||||
|
||||
return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ])
|
||||
return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST ])
|
||||
.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
|
@ -688,8 +702,8 @@ export class VideoModel extends Model<VideoModel> {
|
|||
toFormattedJSON () {
|
||||
let serverHost
|
||||
|
||||
if (this.VideoChannel.Account.Server) {
|
||||
serverHost = this.VideoChannel.Account.Server.host
|
||||
if (this.VideoChannel.Account.Actor.Server) {
|
||||
serverHost = this.VideoChannel.Account.Actor.Server.host
|
||||
} else {
|
||||
// It means it's our video
|
||||
serverHost = CONFIG.WEBSERVER.HOST
|
||||
|
@ -805,9 +819,9 @@ export class VideoModel extends Model<VideoModel> {
|
|||
|
||||
for (const rate of this.AccountVideoRates) {
|
||||
if (rate.type === 'like') {
|
||||
likes.push(rate.Account.url)
|
||||
likes.push(rate.Account.Actor.url)
|
||||
} else if (rate.type === 'dislike') {
|
||||
dislikes.push(rate.Account.url)
|
||||
dislikes.push(rate.Account.Actor.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,7 +834,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
const shares: string[] = []
|
||||
|
||||
for (const videoShare of this.VideoShares) {
|
||||
const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account)
|
||||
const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor)
|
||||
shares.push(shareUrl)
|
||||
}
|
||||
|
||||
|
@ -886,7 +900,13 @@ export class VideoModel extends Model<VideoModel> {
|
|||
url,
|
||||
likes: likesObject,
|
||||
dislikes: dislikesObject,
|
||||
shares: sharesObject
|
||||
shares: sharesObject,
|
||||
attributedTo: [
|
||||
{
|
||||
type: 'Group',
|
||||
id: this.VideoChannel.Actor.url
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1030,8 +1050,8 @@ export class VideoModel extends Model<VideoModel> {
|
|||
baseUrlHttp = CONFIG.WEBSERVER.URL
|
||||
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
|
||||
} else {
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host
|
||||
}
|
||||
|
||||
return { baseUrlHttp, baseUrlWs }
|
||||
|
|
|
@ -184,25 +184,9 @@ describe('Test server follows API validators', function () {
|
|||
.expect(403)
|
||||
})
|
||||
|
||||
it('Should fail with an undefined id', async function () {
|
||||
await request(server.url)
|
||||
.delete(path + '/' + undefined)
|
||||
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it('Should fail with an invalid id', async function () {
|
||||
await request(server.url)
|
||||
.delete(path + '/foobar')
|
||||
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it('Should fail we do not follow this server', async function () {
|
||||
await request(server.url)
|
||||
.delete(path + '/-1')
|
||||
.delete(path + '/example.com')
|
||||
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404)
|
||||
|
|
|
@ -23,7 +23,6 @@ const expect = chai.expect
|
|||
|
||||
describe('Test follows', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
let server3Id: number
|
||||
|
||||
before(async function () {
|
||||
this.timeout(20000)
|
||||
|
@ -82,8 +81,6 @@ describe('Test follows', function () {
|
|||
expect(server3Follow).to.not.be.undefined
|
||||
expect(server2Follow.state).to.equal('accepted')
|
||||
expect(server3Follow.state).to.equal('accepted')
|
||||
|
||||
server3Id = server3Follow.following.id
|
||||
})
|
||||
|
||||
it('Should have 0 followings on server 1 and 2', async function () {
|
||||
|
@ -121,7 +118,7 @@ describe('Test follows', function () {
|
|||
it('Should unfollow server 3 on server 1', async function () {
|
||||
this.timeout(5000)
|
||||
|
||||
await unfollow(servers[0].url, servers[0].accessToken, server3Id)
|
||||
await unfollow(servers[0].url, servers[0].accessToken, servers[2])
|
||||
|
||||
await wait(3000)
|
||||
})
|
||||
|
|
|
@ -42,8 +42,8 @@ async function follow (follower: string, following: string[], accessToken: strin
|
|||
return res
|
||||
}
|
||||
|
||||
async function unfollow (url: string, accessToken: string, id: number, expectedStatus = 204) {
|
||||
const path = '/api/v1/server/following/' + id
|
||||
async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
|
||||
const path = '/api/v1/server/following/' + target.host
|
||||
|
||||
const res = await request(url)
|
||||
.delete(path)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { ActivityPubSignature } from './activitypub-signature'
|
||||
import { VideoChannelObject, VideoTorrentObject } from './objects'
|
||||
import { VideoTorrentObject } from './objects'
|
||||
import { DislikeObject } from './objects/dislike-object'
|
||||
import { VideoAbuseObject } from './objects/video-abuse-object'
|
||||
import { ViewObject } from './objects/view-object'
|
||||
|
||||
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
|
||||
export type Activity = ActivityCreate | ActivityUpdate |
|
||||
ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
|
||||
ActivityUndo | ActivityLike
|
||||
|
||||
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like'
|
||||
export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like'
|
||||
|
||||
export interface ActivityAudience {
|
||||
to: string[]
|
||||
|
@ -27,18 +27,12 @@ export interface BaseActivity {
|
|||
|
||||
export interface ActivityCreate extends BaseActivity {
|
||||
type: 'Create'
|
||||
object: VideoChannelObject | VideoAbuseObject | ViewObject | DislikeObject
|
||||
}
|
||||
|
||||
export interface ActivityAdd extends BaseActivity {
|
||||
type: 'Add'
|
||||
target: string
|
||||
object: VideoTorrentObject
|
||||
object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject
|
||||
}
|
||||
|
||||
export interface ActivityUpdate extends BaseActivity {
|
||||
type: 'Update'
|
||||
object: VideoTorrentObject | VideoChannelObject
|
||||
object: VideoTorrentObject
|
||||
}
|
||||
|
||||
export interface ActivityDelete extends BaseActivity {
|
||||
|
@ -56,7 +50,7 @@ export interface ActivityAccept extends BaseActivity {
|
|||
|
||||
export interface ActivityAnnounce extends BaseActivity {
|
||||
type: 'Announce'
|
||||
object: ActivityCreate | ActivityAdd
|
||||
object: ActivityCreate
|
||||
}
|
||||
|
||||
export interface ActivityUndo extends BaseActivity {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { ActivityPubAttributedTo } from './objects/common-objects'
|
||||
|
||||
export type ActivityPubActorType = 'Person' | 'Application' | 'Group'
|
||||
|
||||
export interface ActivityPubActor {
|
||||
'@context': any[]
|
||||
type: 'Person' | 'Application' | 'Group'
|
||||
type: ActivityPubActorType
|
||||
id: string
|
||||
following: string
|
||||
followers: string
|
||||
|
@ -12,6 +16,8 @@ export interface ActivityPubActor {
|
|||
endpoints: {
|
||||
sharedInbox: string
|
||||
}
|
||||
summary: string
|
||||
attributedTo: ActivityPubAttributedTo[]
|
||||
|
||||
uuid: string
|
||||
publicKey: {
|
||||
|
@ -21,7 +27,6 @@ export interface ActivityPubActor {
|
|||
}
|
||||
|
||||
// Not used
|
||||
// summary: string
|
||||
// icon: string[]
|
||||
// liked: string
|
||||
}
|
||||
|
|
|
@ -23,3 +23,8 @@ export interface ActivityUrlObject {
|
|||
width: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
export interface ActivityPubAttributedTo {
|
||||
type: 'Group' | 'Person'
|
||||
id: string
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export * from './common-objects'
|
||||
export * from './video-abuse-object'
|
||||
export * from './video-channel-object'
|
||||
export * from './video-torrent-object'
|
||||
export * from './view-object'
|
||||
export * from './dislike-object'
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { ActivityPubOrderedCollection } from '../activitypub-ordered-collection'
|
||||
|
||||
export interface VideoChannelObject {
|
||||
type: 'VideoChannel'
|
||||
id: string
|
||||
name: string
|
||||
content: string
|
||||
uuid: string
|
||||
published: string
|
||||
updated: string
|
||||
actor?: string
|
||||
shares?: ActivityPubOrderedCollection<string>
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
ActivityIconObject,
|
||||
ActivityIdentifierObject,
|
||||
ActivityIdentifierObject, ActivityPubAttributedTo,
|
||||
ActivityTagObject,
|
||||
ActivityUrlObject
|
||||
} from './common-objects'
|
||||
|
@ -24,8 +24,8 @@ export interface VideoTorrentObject {
|
|||
content: string
|
||||
icon: ActivityIconObject
|
||||
url: ActivityUrlObject[]
|
||||
actor?: string
|
||||
likes?: ActivityPubOrderedCollection<string>
|
||||
dislikes?: ActivityPubOrderedCollection<string>
|
||||
shares?: ActivityPubOrderedCollection<string>
|
||||
attributedTo: ActivityPubAttributedTo[]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from './accounts'
|
||||
export * from './actors'
|
||||
export * from './activitypub'
|
||||
export * from './users'
|
||||
export * from './videos'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Account } from '../accounts'
|
||||
import { Account } from '../actors'
|
||||
import { VideoChannel } from '../videos/video-channel.model'
|
||||
import { UserRole } from './user-role'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Account } from '../accounts'
|
||||
import { Account } from '../actors'
|
||||
import { VideoChannel } from './video-channel.model'
|
||||
import { VideoPrivacy } from './video-privacy.enum'
|
||||
|
||||
|
|
Loading…
Reference in New Issue