Begin moving video channel to actor
authorChocobozzz <me@florianbigard.com>
Thu, 14 Dec 2017 16:38:41 +0000 (17:38 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 19 Dec 2017 09:53:16 +0000 (10:53 +0100)
103 files changed:
client/src/app/+admin/follows/followers-list/followers-list.component.ts
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/core/auth/auth.service.ts
client/src/app/shared/account/account.model.ts
client/src/app/shared/video/video-details.model.ts
client/src/app/shared/video/video.model.ts
scripts/update-host.ts
server.ts
server/controllers/activitypub/client.ts
server/controllers/activitypub/inbox.ts
server/controllers/activitypub/outbox.ts
server/controllers/api/server/follows.ts
server/controllers/api/users.ts
server/controllers/api/videos/abuse.ts
server/controllers/api/videos/channel.ts
server/controllers/api/videos/index.ts
server/controllers/static.ts
server/controllers/webfinger.ts
server/helpers/activitypub.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/actor.ts
server/helpers/custom-validators/activitypub/announce.ts
server/helpers/custom-validators/activitypub/misc.ts
server/helpers/custom-validators/activitypub/undo.ts
server/helpers/custom-validators/activitypub/video-channels.ts
server/helpers/custom-validators/activitypub/videos.ts
server/helpers/custom-validators/webfinger.ts
server/helpers/peertube-crypto.ts
server/helpers/utils.ts
server/helpers/webfinger.ts
server/initializers/constants.ts
server/initializers/database.ts
server/initializers/installer.ts
server/initializers/migrations/0100-activitypub.ts
server/lib/activitypub/account.ts [deleted file]
server/lib/activitypub/actor.ts [new file with mode: 0644]
server/lib/activitypub/fetch.ts
server/lib/activitypub/index.ts
server/lib/activitypub/process/index.ts
server/lib/activitypub/process/misc.ts
server/lib/activitypub/process/process-accept.ts
server/lib/activitypub/process/process-add.ts [deleted file]
server/lib/activitypub/process/process-announce.ts
server/lib/activitypub/process/process-create.ts
server/lib/activitypub/process/process-delete.ts
server/lib/activitypub/process/process-follow.ts
server/lib/activitypub/process/process-like.ts
server/lib/activitypub/process/process-undo.ts
server/lib/activitypub/process/process-update.ts
server/lib/activitypub/process/process.ts
server/lib/activitypub/send/index.ts
server/lib/activitypub/send/misc.ts
server/lib/activitypub/send/send-accept.ts
server/lib/activitypub/send/send-add.ts [deleted file]
server/lib/activitypub/send/send-announce.ts
server/lib/activitypub/send/send-create.ts
server/lib/activitypub/send/send-delete.ts
server/lib/activitypub/send/send-follow.ts
server/lib/activitypub/send/send-like.ts
server/lib/activitypub/send/send-undo.ts
server/lib/activitypub/send/send-update.ts
server/lib/activitypub/share.ts
server/lib/activitypub/url.ts
server/lib/activitypub/video-channels.ts [deleted file]
server/lib/activitypub/videos.ts
server/lib/index.ts [deleted file]
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
server/lib/user.ts
server/lib/video-channel.ts
server/middlewares/activitypub.ts
server/middlewares/validators/follows.ts
server/middlewares/validators/video-channels.ts
server/middlewares/validators/webfinger.ts
server/models/account/account-follow.ts [deleted file]
server/models/account/account.ts
server/models/account/user.ts
server/models/activitypub/actor-follow.ts [new file with mode: 0644]
server/models/activitypub/actor.ts
server/models/application/application.ts
server/models/video/video-abuse.ts
server/models/video/video-channel-share.ts [deleted file]
server/models/video/video-channel.ts
server/models/video/video-share.ts
server/models/video/video.ts
server/tests/api/check-params/follows.ts
server/tests/api/follows.ts
server/tests/utils/follows.ts
shared/models/accounts/account.model.ts [deleted file]
shared/models/accounts/follow.model.ts [deleted file]
shared/models/accounts/index.ts [deleted file]
shared/models/activitypub/activity.ts
shared/models/activitypub/activitypub-actor.ts
shared/models/activitypub/objects/common-objects.ts
shared/models/activitypub/objects/index.ts
shared/models/activitypub/objects/video-channel-object.ts [deleted file]
shared/models/activitypub/objects/video-torrent-object.ts
shared/models/actors/account.model.ts [new file with mode: 0644]
shared/models/actors/follow.model.ts [new file with mode: 0644]
shared/models/actors/index.ts [new file with mode: 0644]
shared/models/index.ts
shared/models/users/user.model.ts
shared/models/videos/video.model.ts

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