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'
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'
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'
-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'
-import { Account } from '../../../../../shared/models/accounts'
+import { Account } from '../../../../../shared/models/actors'
import { Video } from '../../shared/video/video.model'
import { AuthUser } from '../../core'
import {
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 {
-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
// ----------- 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 -----------
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()
executeIfActivityPub(asyncMiddleware(videoChannelController))
)
-activityPubClientRouter.get('/video-channels/:id/announces/:accountId',
- executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)),
- executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController))
-)
-
// ---------------------------------------------------------------------------
export {
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)
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)
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)
}
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()
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()
}
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'
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)
}
}
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()
}
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,
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()
asyncMiddleware(followRetry)
)
-serverFollowsRouter.delete('/following/:accountId',
+serverFollowsRouter.delete('/following/:host',
authenticate,
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
asyncMiddleware(removeFollowingValidator),
// ---------------------------------------------------------------------------
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)
}
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)
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 }
-}
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,
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'
// 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)
}
})
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,
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) {
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)
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) {
resetSequelizeInstance,
retryTransactionWrapper
} from '../../../helpers'
-import { getServerAccount } from '../../../helpers/utils'
+import { getServerActor } from '../../../helpers/utils'
import {
CONFIG,
sequelizeTypescript,
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,
// 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)
// 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)
}
})
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)
-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()
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()
// ---------------------------------------------------------------------------
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
}
]
}
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) {
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>
}
// ---------------------------------------------------------------------------
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'
const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
Create: checkCreateActivity,
- Add: checkAddActivity,
Update: checkUpdateActivity,
Delete: checkDeleteActivity,
Follow: checkFollowActivity,
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)
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) {
+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)
}
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) {
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) {
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 {
isActorFollowersCountValid,
isActorFollowActivityValid,
isActorAcceptActivityValid,
- isActorDeleteActivityValid
+ isActorDeleteActivityValid,
+ isActorIdExist,
+ isLocalActorNameExist,
+ isActorNameValid,
+ isActorExist
}
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)
)
}
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) {
)
}
+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
}
-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)
)
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)
// ---------------------------------------------------------------------------
export {
- isVideoChannelCreateActivityValid,
isVideoChannelUpdateActivityValid,
isVideoChannelDeleteActivityValid,
isVideoChannelObjectValid
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)
}
}
function isVideoTorrentObjectValid (video: any) {
+ console.log(video)
+
return video.type === 'Video' &&
isActivityPubUrlValid(video.id) &&
isVideoNameValid(video.name) &&
(!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
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
}
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'
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 ]
}
})
}
-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)
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'
})
}
-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 }
isSignupAllowed,
computeResolutionsToTranscode,
resetSequelizeInstance,
- getServerAccount,
+ getServerActor,
SortType
}
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'
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
- if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.')
-
- const selfLink = webfingerData.links.find(l => l.rel === 'self')
- if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) {
- 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
+ const webfingerData: WebFingerData = await webfingerLookup(name + '@' + host)
+ return getLinkOrThrow(webfingerData)
}
// ---------------------------------------------------------------------------
export {
- getAccountFromWebfinger
+ 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')
+ if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) {
+ throw new Error('Cannot find self link or href is not a valid URL.')
+ }
+
+ return selfLink.href
+}
+
function webfingerLookup (nameWithHost: string) {
return new Promise<WebFingerData>((res, rej) => {
webfinger.lookup(nameWithHost, (err, p) => {
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'
// ---------------------------------------------------------------------------
-const SERVER_ACCOUNT_NAME = 'peertube'
+const SERVER_ACTOR_NAME = 'peertube'
const ACTIVITY_PUB = {
POTENTIAL_ACCEPT_HEADERS: [
}
}
+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
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,
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'
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'
async function initDatabaseModels (silent: boolean) {
sequelizeTypescript.addModels([
ApplicationModel,
+ ActorModel,
+ ActorFollowModel,
AvatarModel,
AccountModel,
JobModel,
ServerModel,
TagModel,
AccountVideoRateModel,
- AccountFollowModel,
UserModel,
VideoAbuseModel,
VideoChannelModel,
- VideoChannelShareModel,
VideoShareModel,
VideoFileModel,
VideoBlacklistModel,
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 () {
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 { publicKey, privateKey } = await createPrivateAndPublicKeys()
- accountCreated.set('publicKey', publicKey)
- accountCreated.set('privateKey', privateKey)
+ const application = await ApplicationModel.create({
+ migrationVersion: LAST_MIGRATION_VERSION
+ })
- return accountCreated.save()
+ return createApplicationActor(application.id)
}
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,
// 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)
+++ /dev/null
-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
-}
--- /dev/null
+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 })
+}
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)
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'
export * from './process'
export * from './process-accept'
-export * from './process-add'
export * from './process-announce'
export * from './process-create'
export * from './process-delete'
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,
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
}
}
}
-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
}
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)
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-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)
}
+++ /dev/null
-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
-}
-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(
// ---------------------------------------------------------------------------
-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
}
if (video.isOwned() && created === true) {
// Don't resend the activity to the sender
- const exceptions = [ accountAnnouncer ]
+ const exceptions = [ actorAnnouncer ]
await forwardActivity(activity, t, exceptions)
}
-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 })
// ---------------------------------------------------------------------------
-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: [ 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: [ byAccount, activity ],
+ 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)
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')
}
}
-async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
- 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) {
+function processCreateVideoAbuse (actor: ActorModel, 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) {
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.')
- {
- let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
- if (videoObject !== undefined) {
- return processDeleteVideo(account, videoObject)
+ 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 videoChannelObject = await VideoChannelModel.loadByUrl(activity.id)
- if (videoChannelObject !== undefined) {
- return processDeleteVideoChannel(account, videoChannelObject)
+ let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
+ if (videoObject !== undefined) {
+ return processDeleteVideo(actor, videoObject)
}
}
// ---------------------------------------------------------------------------
-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 })
logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
}
-async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
+async function processDeleteAccount (accountToRemove: AccountModel) {
const options = {
- arguments: [ account, videoChannelToRemove ],
- errorMessage: 'Cannot remove the remote video channel with many retries.'
+ arguments: [ accountToRemove ],
+ errorMessage: 'Cannot remove the remote account with many retries.'
}
- await retryTransactionWrapper(deleteRemoteVideoChannel, options)
+ await retryTransactionWrapper(deleteRemoteAccount, options)
}
-async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
- logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
+async function deleteRemoteAccount (accountToRemove: AccountModel) {
+ logger.debug('Removing remote account "%s".', accountToRemove.Actor.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 })
+ await accountToRemove.destroy({ transaction: t })
})
- logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
+ logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid)
}
-async function processDeleteAccount (accountToRemove: AccountModel) {
+async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
const options = {
- arguments: [ accountToRemove ],
- errorMessage: 'Cannot remove the remote account with many retries.'
+ arguments: [ videoChannelToRemove ],
+ errorMessage: 'Cannot remove the remote video channel with many retries.'
}
- await retryTransactionWrapper(deleteRemoteAccount, options)
+ await retryTransactionWrapper(deleteRemoteVideoChannel, options)
}
-async function deleteRemoteAccount (accountToRemove: AccountModel) {
- logger.debug('Removing remote account "%s".', accountToRemove.uuid)
+async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel) {
+ logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid)
await sequelizeTypescript.transaction(async t => {
- await accountToRemove.destroy({ transaction: t })
+ await videoChannelToRemove.destroy({ transaction: t })
})
- logger.info('Remote account with uuid %s removed.', accountToRemove.uuid)
+ logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
}
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)
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-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)
}
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)
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-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)
if (video.isOwned() && created === true) {
// Don't resend the activity to the sender
- const exceptions = [ byAccount ]
+ const exceptions = [ byActor ]
await forwardActivity(activity, t, exceptions)
}
})
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'
// ---------------------------------------------------------------------------
-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)
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)
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
})
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
// ---------------------------------------------------------------------------
-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
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)
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)
-}
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'
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,
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
}
}
try {
- await activityProcessor(activity, inboxAccount)
+ await activityProcessor(activity, inboxActor)
} catch (err) {
logger.warn('Cannot process activity %s.', activity.type, err)
}
export * from './send-accept'
-export * from './send-add'
export * from './send-announce'
export * from './send-create'
export * from './send-delete'
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 || []
}
}
- 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
}
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
}
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)
-
- return accountsToForwardView
-}
-
-async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
- const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
- accountsToForwardView.push(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 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 = []
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)
}
export {
broadcastToFollowers,
- getOriginVideoChannelAudience,
unicastTo,
getAudience,
getOriginVideoAudience,
- getAccountsInvolvedInVideo,
- getAccountsInvolvedInVideoChannel,
+ getActorsInvolvedInVideo,
getObjectFollowersAudience,
forwardActivity
}
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)
// ---------------------------------------------------------------------------
-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
}
+++ /dev/null
-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
-}
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 {
to: audience.to,
cc: audience.cc,
id: url,
- actor: byAccount.url,
+ actor: byActor.url,
object
}
}
export {
sendVideoAnnounceToFollowers,
- sendVideoChannelAnnounceToFollowers,
sendVideoAnnounceToOrigin,
- sendVideoChannelAnnounceToOrigin,
announceActivityData,
- buildVideoAnnounceToFollowers,
- buildVideoChannelAnnounceToFollowers
+ buildVideoAnnounceToFollowers
}
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
}
}
// ---------------------------------------------------------------------------
export {
- sendCreateVideoChannel,
+ sendCreateVideo,
sendVideoAbuse,
createActivityData,
sendCreateViewToOrigin,
// ---------------------------------------------------------------------------
-function createViewActivityData (byAccount: AccountModel, video: VideoModel) {
+function createViewActivityData (byActor: ActorModel, video: VideoModel) {
return {
type: 'View',
- actor: byAccount.url,
+ actor: byActor.url,
object: video.url
}
}
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
}
}
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
}
}
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
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,
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)
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)
}
// ---------------------------------------------------------------------------
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
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 data = await updateActivityData(url, byActor, videoObject, t, audience)
- 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)
}
// ---------------------------------------------------------------------------
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
}
}
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
}
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) {
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) {
}
export {
+ getApplicationActivityPubUrl,
getVideoActivityPubUrl,
getVideoChannelActivityPubUrl,
getAccountActivityPubUrl,
getVideoAbuseActivityPubUrl,
- getAccountFollowActivityPubUrl,
- getAccountFollowAcceptActivityPubUrl,
+ getActorFollowActivityPubUrl,
+ getActorFollowAcceptActivityPubUrl,
getAnnounceActivityPubUrl,
getUpdateActivityPubUrl,
getUndoActivityPubUrl,
+++ /dev/null
-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
-}
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)
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,
}
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 {
+++ /dev/null
-export * from './activitypub'
-export * from './cache'
-export * from './jobs'
-export * from './oauth-model'
-export * from './user'
-export * from './video-channel'
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'
type ActivityPubHttpPayload = {
uris: string[]
- signatureAccountId?: number
+ signatureActorId?: number
body?: any
attemptNumber?: number
}
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
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'
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()
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) {
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({
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
}
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
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()
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 = [
]
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)
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,
} 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 = [
}
]
-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 {
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()
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 = [
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()
}
]
+++ /dev/null
-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
- }
- }
-}
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'
import { VideoChannelModel } from '../video/video-channel'
import { UserModel } from './user'
-@Table({
- tableName: 'account',
- indexes: [
- {
- fields: [ 'name' ]
- },
- {
- fields: [ 'serverId' ]
- },
- {
- fields: [ 'userId' ],
- unique: true
- },
- {
- fields: [ 'applicationId' ],
- unique: true
- },
+@DefaultScope({
+ include: [
{
- 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
},
onDelete: 'cascade'
})
- Application: ApplicationModel
+ Account: ApplicationModel
@HasMany(() => VideoChannelModel, {
foreignKey: {
@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)
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: [
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
}
}
toActivityPubObject () {
- return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account')
+ return this.Actor.toActivityPubObject(this.name, 'Account')
}
isOwned () {
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'
--- /dev/null
+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
+ }
+ }
+}
+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)
uuid: string
@AllowNull(false)
- @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name'))
+ @Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name'))
@Column
name: string
})
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
})
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: {
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 () {
return {
id: this.id,
+ uuid: this.uuid,
host,
score,
followingCount: this.followingCount,
}
}
- 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,
attributes: [ 'sharedInboxUrl' ],
include: [
{
- model: AccountFollowModel,
+ model: ActorFollowModel,
required: true,
as: 'followers',
where: {
- targetAccountId: this.id
+ targetActorId: this.id
}
}
],
-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'
})
@Column
migrationVersion: number
+ @HasOne(() => AccountModel, {
+ foreignKey: {
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+ Account: AccountModel
+
static countTotal () {
return ApplicationModel.count()
}
+
+ static load () {
+ return ApplicationModel.findOne()
+ }
}
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'
include: [
{
model: AccountModel,
- required: true,
- include: [
- {
- model: ServerModel,
- required: false
- }
- ]
+ required: true
},
{
model: VideoModel,
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
+++ /dev/null
-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))
- }
-}
-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
+ }
+ ]
}
]
},
include: [
() => VideoModel
]
+ },
+ [ScopeNames.WITH_ACTOR]: {
+ include: [
+ () => ActorModel
+ ]
}
})
@Table({
})
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
@Column
description: string
- @AllowNull(false)
- @Column
- remote: boolean
-
@CreatedAt
createdAt: Date
})
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
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 }
})
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: {
}
}
- 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) {
]
}
- 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
+ }
+ ]
+ })
}
}
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
},
{
}
]
},
- [ScopeNames.WITH_ACCOUNT]: {
+ [ScopeNames.WITH_ACTOR]: {
include: [
{
- model: () => AccountModel,
+ model: () => ActorModel,
required: true
}
]
tableName: 'videoShare',
indexes: [
{
- fields: [ 'accountId' ]
+ fields: [ 'actorId' ]
},
{
fields: [ 'videoId' ]
@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
})
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
}
],
}
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
- .then(res => res.map(r => r.Account))
+ .then(res => res.map(r => r.Actor))
}
}
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'
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',
}
@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
}
},
required: true,
include: [
{
- model: () => ServerModel,
- required: false
+ model: () => ActorModel,
+ required: true,
+ include: [
+ {
+ model: () => ServerModel,
+ required: false
+ }
+ ]
}
]
}
include: [
{
model: () => VideoShareModel,
- include: [ () => AccountModel ]
+ include: [ () => ActorModel ]
}
]
},
@BelongsTo(() => VideoChannelModel, {
foreignKey: {
- allowNull: false
+ allowNull: true
},
onDelete: 'cascade'
})
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})`
}
}
},
{
- accountId
+ actorId
}
]
},
- include: [ AccountModel ]
+ include: [
+ {
+ model: ActorModel,
+ required: true
+ }
+ ]
},
{
model: VideoChannelModel,
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 {
const accountInclude: IIncludeOptions = {
model: AccountModel,
- include: [ serverInclude ]
+ include: [
+ {
+ model: ActorModel,
+ required: true,
+ include: [ serverInclude ]
+ }
+ ]
}
const videoChannelInclude: IIncludeOptions = {
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,
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
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)
}
}
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)
}
url,
likes: likesObject,
dislikes: dislikesObject,
- shares: sharesObject
+ shares: sharesObject,
+ attributedTo: [
+ {
+ type: 'Group',
+ id: this.VideoChannel.Actor.url
+ }
+ ]
}
}
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 }
.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)
describe('Test follows', function () {
let servers: ServerInfo[] = []
- let server3Id: number
before(async function () {
this.timeout(20000)
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 () {
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)
})
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)
+++ /dev/null
-import { Avatar } from '../avatars/avatar.model'
-
-export interface Account {
- id: number
- uuid: string
- name: string
- host: string
- followingCount: number
- followersCount: number
- createdAt: Date
- updatedAt: Date
- avatar: Avatar
-}
+++ /dev/null
-import { Account } from './account.model'
-
-export type FollowState = 'pending' | 'accepted'
-
-export interface AccountFollow {
- id: number
- follower: Account
- following: Account
- state: FollowState
- createdAt: Date
- updatedAt: Date
-}
+++ /dev/null
-export * from './account.model'
-export * from './follow.model'
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[]
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 {
export interface ActivityAnnounce extends BaseActivity {
type: 'Announce'
- object: ActivityCreate | ActivityAdd
+ object: ActivityCreate
}
export interface ActivityUndo extends BaseActivity {
+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
endpoints: {
sharedInbox: string
}
+ summary: string
+ attributedTo: ActivityPubAttributedTo[]
uuid: string
publicKey: {
}
// Not used
- // summary: string
// icon: string[]
// liked: string
}
width: number
size?: number
}
+
+export interface ActivityPubAttributedTo {
+ type: 'Group' | 'Person'
+ id: string
+}
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'
+++ /dev/null
-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>
-}
import {
ActivityIconObject,
- ActivityIdentifierObject,
+ ActivityIdentifierObject, ActivityPubAttributedTo,
ActivityTagObject,
ActivityUrlObject
} from './common-objects'
content: string
icon: ActivityIconObject
url: ActivityUrlObject[]
- actor?: string
likes?: ActivityPubOrderedCollection<string>
dislikes?: ActivityPubOrderedCollection<string>
shares?: ActivityPubOrderedCollection<string>
+ attributedTo: ActivityPubAttributedTo[]
}
--- /dev/null
+import { Avatar } from '../avatars/avatar.model'
+
+export interface Account {
+ id: number
+ uuid: string
+ name: string
+ host: string
+ followingCount: number
+ followersCount: number
+ createdAt: Date
+ updatedAt: Date
+ avatar: Avatar
+}
--- /dev/null
+import { Account } from './account.model'
+
+export type FollowState = 'pending' | 'accepted'
+
+export interface AccountFollow {
+ id: number
+ follower: Account
+ following: Account
+ state: FollowState
+ createdAt: Date
+ updatedAt: Date
+}
--- /dev/null
+export * from './account.model'
+export * from './follow.model'
-export * from './accounts'
+export * from './actors'
export * from './activitypub'
export * from './users'
export * from './videos'
-import { Account } from '../accounts'
+import { Account } from '../actors'
import { VideoChannel } from '../videos/video-channel.model'
import { UserRole } from './user-role'
-import { Account } from '../accounts'
+import { Account } from '../actors'
import { VideoChannel } from './video-channel.model'
import { VideoPrivacy } from './video-privacy.enum'