aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.ts2
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts2
-rw-r--r--client/src/app/core/auth/auth.service.ts2
-rw-r--r--client/src/app/shared/account/account.model.ts2
-rw-r--r--client/src/app/shared/video/video-details.model.ts2
-rw-r--r--client/src/app/shared/video/video.model.ts2
-rwxr-xr-xscripts/update-host.ts8
-rw-r--r--server.ts3
-rw-r--r--server/controllers/activitypub/client.ts31
-rw-r--r--server/controllers/activitypub/inbox.ts10
-rw-r--r--server/controllers/activitypub/outbox.ts20
-rw-r--r--server/controllers/api/server/follows.ts114
-rw-r--r--server/controllers/api/users.ts2
-rw-r--r--server/controllers/api/videos/abuse.ts20
-rw-r--r--server/controllers/api/videos/channel.ts28
-rw-r--r--server/controllers/api/videos/index.ts13
-rw-r--r--server/controllers/static.ts10
-rw-r--r--server/controllers/webfinger.ts8
-rw-r--r--server/helpers/activitypub.ts6
-rw-r--r--server/helpers/custom-validators/activitypub/activity.ts19
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts61
-rw-r--r--server/helpers/custom-validators/activitypub/announce.ts6
-rw-r--r--server/helpers/custom-validators/activitypub/misc.ts20
-rw-r--r--server/helpers/custom-validators/activitypub/undo.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/video-channels.ts6
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts14
-rw-r--r--server/helpers/custom-validators/webfinger.ts8
-rw-r--r--server/helpers/peertube-crypto.ts18
-rw-r--r--server/helpers/utils.ts20
-rw-r--r--server/helpers/webfinger.ts33
-rw-r--r--server/initializers/constants.ts14
-rw-r--r--server/initializers/database.ts8
-rw-r--r--server/initializers/installer.ts17
-rw-r--r--server/initializers/migrations/0100-activitypub.ts4
-rw-r--r--server/lib/activitypub/account.ts127
-rw-r--r--server/lib/activitypub/actor.ts229
-rw-r--r--server/lib/activitypub/fetch.ts6
-rw-r--r--server/lib/activitypub/index.ts3
-rw-r--r--server/lib/activitypub/process/index.ts1
-rw-r--r--server/lib/activitypub/process/misc.ts52
-rw-r--r--server/lib/activitypub/process/process-accept.ts18
-rw-r--r--server/lib/activitypub/process/process-add.ts137
-rw-r--r--server/lib/activitypub/process/process-announce.ts67
-rw-r--r--server/lib/activitypub/process/process-create.ts187
-rw-r--r--server/lib/activitypub/process/process-delete.ts72
-rw-r--r--server/lib/activitypub/process/process-follow.ts48
-rw-r--r--server/lib/activitypub/process/process-like.ts19
-rw-r--r--server/lib/activitypub/process/process-undo.ts43
-rw-r--r--server/lib/activitypub/process/process-update.ts63
-rw-r--r--server/lib/activitypub/process/process.ts14
-rw-r--r--server/lib/activitypub/send/index.ts1
-rw-r--r--server/lib/activitypub/send/misc.ts80
-rw-r--r--server/lib/activitypub/send/send-accept.ts22
-rw-r--r--server/lib/activitypub/send/send-add.ts45
-rw-r--r--server/lib/activitypub/send/send-announce.ts84
-rw-r--r--server/lib/activitypub/send/send-create.ts107
-rw-r--r--server/lib/activitypub/send/send-delete.ts38
-rw-r--r--server/lib/activitypub/send/send-follow.ts20
-rw-r--r--server/lib/activitypub/send/send-like.ts34
-rw-r--r--server/lib/activitypub/send/send-undo.ts84
-rw-r--r--server/lib/activitypub/send/send-update.ts52
-rw-r--r--server/lib/activitypub/share.ts24
-rw-r--r--server/lib/activitypub/url.ts46
-rw-r--r--server/lib/activitypub/video-channels.ts59
-rw-r--r--server/lib/activitypub/videos.ts28
-rw-r--r--server/lib/index.ts6
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts12
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts5
-rw-r--r--server/lib/user.ts45
-rw-r--r--server/lib/video-channel.ts19
-rw-r--r--server/middlewares/activitypub.ts28
-rw-r--r--server/middlewares/validators/follows.ts13
-rw-r--r--server/middlewares/validators/video-channels.ts31
-rw-r--r--server/middlewares/validators/webfinger.ts10
-rw-r--r--server/models/account/account-follow.ts228
-rw-r--r--server/models/account/account.ts111
-rw-r--r--server/models/account/user.ts23
-rw-r--r--server/models/activitypub/actor-follow.ts260
-rw-r--r--server/models/activitypub/actor.ts165
-rw-r--r--server/models/application/application.ts23
-rw-r--r--server/models/video/video-abuse.ts13
-rw-r--r--server/models/video/video-channel-share.ts96
-rw-r--r--server/models/video/video-channel.ts164
-rw-r--r--server/models/video/video-share.ts32
-rw-r--r--server/models/video/video.ts78
-rw-r--r--server/tests/api/check-params/follows.ts18
-rw-r--r--server/tests/api/follows.ts5
-rw-r--r--server/tests/utils/follows.ts4
-rw-r--r--shared/models/activitypub/activity.ts18
-rw-r--r--shared/models/activitypub/activitypub-actor.ts9
-rw-r--r--shared/models/activitypub/objects/common-objects.ts5
-rw-r--r--shared/models/activitypub/objects/index.ts1
-rw-r--r--shared/models/activitypub/objects/video-channel-object.ts13
-rw-r--r--shared/models/activitypub/objects/video-torrent-object.ts4
-rw-r--r--shared/models/actors/account.model.ts (renamed from shared/models/accounts/account.model.ts)0
-rw-r--r--shared/models/actors/follow.model.ts (renamed from shared/models/accounts/follow.model.ts)0
-rw-r--r--shared/models/actors/index.ts (renamed from shared/models/accounts/index.ts)0
-rw-r--r--shared/models/index.ts2
-rw-r--r--shared/models/users/user.model.ts2
-rw-r--r--shared/models/videos/video.model.ts2
100 files changed, 1756 insertions, 2036 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index 8dc2d9317..649815709 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -2,7 +2,7 @@ import { Component } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications' 3import { NotificationsService } from 'angular2-notifications'
4import { SortMeta } from 'primeng/primeng' 4import { SortMeta } from 'primeng/primeng'
5import { AccountFollow } from '../../../../../../shared/models/accounts/follow.model' 5import { AccountFollow } from '../../../../../../shared/models/actors/follow.model'
6import { RestPagination, RestTable } from '../../../shared' 6import { RestPagination, RestTable } from '../../../shared'
7import { FollowService } from '../shared' 7import { FollowService } from '../shared'
8 8
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index 411b8f640..d4f8d0309 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -1,7 +1,7 @@
1import { Component } from '@angular/core' 1import { Component } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { SortMeta } from 'primeng/primeng' 3import { SortMeta } from 'primeng/primeng'
4import { AccountFollow } from '../../../../../../shared/models/accounts/follow.model' 4import { AccountFollow } from '../../../../../../shared/models/actors/follow.model'
5import { ConfirmService } from '../../../core/confirm/confirm.service' 5import { ConfirmService } from '../../../core/confirm/confirm.service'
6import { RestPagination, RestTable } from '../../../shared' 6import { RestPagination, RestTable } from '../../../shared'
7import { FollowService } from '../shared' 7import { FollowService } from '../shared'
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 37264a8ad..c914848ae 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -10,7 +10,7 @@ import { Observable } from 'rxjs/Observable'
10import { ReplaySubject } from 'rxjs/ReplaySubject' 10import { ReplaySubject } from 'rxjs/ReplaySubject'
11import { Subject } from 'rxjs/Subject' 11import { Subject } from 'rxjs/Subject'
12import { OAuthClientLocal, User as UserServerModel, UserRefreshToken, UserRole, VideoChannel } from '../../../../../shared' 12import { OAuthClientLocal, User as UserServerModel, UserRefreshToken, UserRole, VideoChannel } from '../../../../../shared'
13import { Account } from '../../../../../shared/models/accounts' 13import { Account } from '../../../../../shared/models/actors'
14import { UserLogin } from '../../../../../shared/models/users/user-login.model' 14import { UserLogin } from '../../../../../shared/models/users/user-login.model'
15import { environment } from '../../../environments/environment' 15import { environment } from '../../../environments/environment'
16import { RestExtractor } from '../../shared/rest' 16import { RestExtractor } from '../../shared/rest'
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts
index 3a29ec979..bacaa208a 100644
--- a/client/src/app/shared/account/account.model.ts
+++ b/client/src/app/shared/account/account.model.ts
@@ -1,4 +1,4 @@
1import { Account as ServerAccount } from '../../../../../shared/models/accounts/account.model' 1import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model'
2import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 2import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
3import { environment } from '../../../environments/environment' 3import { environment } from '../../../environments/environment'
4 4
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index d51bc01a7..8243b9f1c 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,4 +1,4 @@
1import { Account } from '../../../../../shared/models/accounts' 1import { Account } from '../../../../../shared/models/actors'
2import { Video } from '../../shared/video/video.model' 2import { Video } from '../../shared/video/video.model'
3import { AuthUser } from '../../core' 3import { AuthUser } from '../../core'
4import { 4import {
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index c3759cb65..f159464c5 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -1,6 +1,6 @@
1import { User } from '../' 1import { User } from '../'
2import { Video as VideoServerModel } from '../../../../../shared' 2import { Video as VideoServerModel } from '../../../../../shared'
3import { Account } from '../../../../../shared/models/accounts' 3import { Account } from '../../../../../shared/models/actors'
4import { environment } from '../../../environments/environment' 4import { environment } from '../../../environments/environment'
5 5
6export class Video implements VideoServerModel { 6export class Video implements VideoServerModel {
diff --git a/scripts/update-host.ts b/scripts/update-host.ts
index eccf203ea..4551a4702 100755
--- a/scripts/update-host.ts
+++ b/scripts/update-host.ts
@@ -1,14 +1,14 @@
1import { getServerAccount } from '../server/helpers' 1import { getServerActor } from '../server/helpers'
2import { initDatabaseModels } from '../server/initializers' 2import { initDatabaseModels } from '../server/initializers'
3import { AccountFollowModel } from '../server/models/account/account-follow' 3import { ActorFollowModel } from '../server/models/activitypub/actor-follow'
4import { VideoModel } from '../server/models/video/video' 4import { VideoModel } from '../server/models/video/video'
5 5
6initDatabaseModels(true) 6initDatabaseModels(true)
7 .then(() => { 7 .then(() => {
8 return getServerAccount() 8 return getServerActor()
9 }) 9 })
10 .then(serverAccount => { 10 .then(serverAccount => {
11 return AccountFollowModel.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined) 11 return ActorFollowModel.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined)
12 }) 12 })
13 .then(res => { 13 .then(res => {
14 return res.total > 0 14 return res.total > 0
diff --git a/server.ts b/server.ts
index a89cdd69a..f64c4ac53 100644
--- a/server.ts
+++ b/server.ts
@@ -50,7 +50,8 @@ migrate()
50 50
51// ----------- PeerTube modules ----------- 51// ----------- PeerTube modules -----------
52import { installApplication } from './server/initializers' 52import { installApplication } from './server/initializers'
53import { activitypubHttpJobScheduler, transcodingJobScheduler, VideosPreviewCache } from './server/lib' 53import { activitypubHttpJobScheduler, transcodingJobScheduler } from './server/lib/jobs'
54import { VideosPreviewCache } from './server/lib/cache'
54import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers' 55import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers'
55 56
56// ----------- Command line ----------- 57// ----------- Command line -----------
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 72b216254..8c6294ff7 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -2,20 +2,13 @@
2import * as express from 'express' 2import * as express from 'express'
3import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers' 3import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers'
4import { ACTIVITY_PUB, CONFIG } from '../../initializers' 4import { ACTIVITY_PUB, CONFIG } from '../../initializers'
5import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send' 5import { buildVideoAnnounceToFollowers } from '../../lib/activitypub/send'
6import { buildVideoAnnounceToFollowers } from '../../lib/index'
7import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares' 6import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares'
8import { 7import { videoChannelsGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators'
9 videoChannelsGetValidator,
10 videoChannelsShareValidator,
11 videosGetValidator,
12 videosShareValidator
13} from '../../middlewares/validators'
14import { AccountModel } from '../../models/account/account' 8import { AccountModel } from '../../models/account/account'
15import { AccountFollowModel } from '../../models/account/account-follow' 9import { ActorFollowModel } from '../../models/activitypub/actor-follow'
16import { VideoModel } from '../../models/video/video' 10import { VideoModel } from '../../models/video/video'
17import { VideoChannelModel } from '../../models/video/video-channel' 11import { VideoChannelModel } from '../../models/video/video-channel'
18import { VideoChannelShareModel } from '../../models/video/video-channel-share'
19import { VideoShareModel } from '../../models/video/video-share' 12import { VideoShareModel } from '../../models/video/video-share'
20 13
21const activityPubClientRouter = express.Router() 14const activityPubClientRouter = express.Router()
@@ -50,11 +43,6 @@ activityPubClientRouter.get('/video-channels/:id',
50 executeIfActivityPub(asyncMiddleware(videoChannelController)) 43 executeIfActivityPub(asyncMiddleware(videoChannelController))
51) 44)
52 45
53activityPubClientRouter.get('/video-channels/:id/announces/:accountId',
54 executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)),
55 executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController))
56)
57
58// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
59 47
60export { 48export {
@@ -75,7 +63,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
75 const page = req.query.page || 1 63 const page = req.query.page || 1
76 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 64 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
77 65
78 const result = await AccountFollowModel.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count) 66 const result = await ActorFollowModel.listAcceptedFollowerUrlsForApi([ account.Actor.id ], undefined, start, count)
79 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) 67 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
80 68
81 return res.json(activityPubResult) 69 return res.json(activityPubResult)
@@ -87,7 +75,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
87 const page = req.query.page || 1 75 const page = req.query.page || 1
88 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 76 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
89 77
90 const result = await AccountFollowModel.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count) 78 const result = await ActorFollowModel.listAcceptedFollowingUrlsForApi([ account.Actor.id ], undefined, start, count)
91 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) 79 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
92 80
93 return res.json(activityPubResult) 81 return res.json(activityPubResult)
@@ -101,14 +89,7 @@ function videoController (req: express.Request, res: express.Response, next: exp
101 89
102async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { 90async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
103 const share = res.locals.videoShare as VideoShareModel 91 const share = res.locals.videoShare as VideoShareModel
104 const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined) 92 const object = await buildVideoAnnounceToFollowers(share.Actor, res.locals.video, undefined)
105
106 return res.json(object)
107}
108
109async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
110 const share = res.locals.videoChannelShare as VideoChannelShareModel
111 const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined)
112 93
113 return res.json(object) 94 return res.json(object)
114} 95}
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index 88a0834f6..8332eabb1 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -5,6 +5,7 @@ import { isActivityValid } from '../../helpers/custom-validators/activitypub/act
5import { processActivities } from '../../lib/activitypub/process/process' 5import { processActivities } from '../../lib/activitypub/process/process'
6import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares' 6import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' 7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
8import { ActorModel } from '../../models/activitypub/actor'
8 9
9const inboxRouter = express.Router() 10const inboxRouter = express.Router()
10 11
@@ -48,7 +49,14 @@ async function inboxController (req: express.Request, res: express.Response, nex
48 activities = activities.filter(a => isActivityValid(a)) 49 activities = activities.filter(a => isActivityValid(a))
49 logger.debug('We keep %d activities.', activities.length, { activities }) 50 logger.debug('We keep %d activities.', activities.length, { activities })
50 51
51 await processActivities(activities, res.locals.signature.account, res.locals.account) 52 let specificActor: ActorModel = undefined
53 if (res.locals.account) {
54 specificActor = res.locals.account
55 } else if (res.locals.videoChannel) {
56 specificActor = res.locals.videoChannel
57 }
58
59 await processActivities(activities, res.locals.signature.actor, specificActor)
52 60
53 res.status(204).end() 61 res.status(204).end()
54} 62}
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 6ed8a3454..01ba253c6 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -3,9 +3,8 @@ import { Activity } from '../../../shared/models/activitypub/activity'
3import { activityPubCollectionPagination } from '../../helpers/activitypub' 3import { activityPubCollectionPagination } from '../../helpers/activitypub'
4import { pageToStartAndCount } from '../../helpers/core-utils' 4import { pageToStartAndCount } from '../../helpers/core-utils'
5import { ACTIVITY_PUB } from '../../initializers/constants' 5import { ACTIVITY_PUB } from '../../initializers/constants'
6import { addActivityData } from '../../lib/activitypub/send/send-add' 6import { announceActivityData, createActivityData } from '../../lib/activitypub/send'
7import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' 7import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
8import { announceActivityData } from '../../lib/index'
9import { asyncMiddleware, localAccountValidator } from '../../middlewares' 8import { asyncMiddleware, localAccountValidator } from '../../middlewares'
10import { AccountModel } from '../../models/account/account' 9import { AccountModel } from '../../models/account/account'
11import { VideoModel } from '../../models/video/video' 10import { VideoModel } from '../../models/video/video'
@@ -27,29 +26,30 @@ export {
27 26
28async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { 27async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
29 const account: AccountModel = res.locals.account 28 const account: AccountModel = res.locals.account
29 const actor = account.Actor
30 30
31 const page = req.query.page || 1 31 const page = req.query.page || 1
32 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 32 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
33 33
34 const data = await VideoModel.listAllAndSharedByAccountForOutbox(account.id, start, count) 34 const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
35 const activities: Activity[] = [] 35 const activities: Activity[] = []
36 36
37 for (const video of data.data) { 37 for (const video of data.data) {
38 const videoObject = video.toActivityPubObject() 38 const videoObject = video.toActivityPubObject()
39 39
40 // This is a shared video
41 const videoChannel = video.VideoChannel 40 const videoChannel = video.VideoChannel
41 // This is a shared video
42 if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { 42 if (video.VideoShares !== undefined && video.VideoShares.length !== 0) {
43 const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.Actor.url, videoObject, undefined) 43 const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
44 44
45 const url = getAnnounceActivityPubUrl(video.url, account) 45 const url = getAnnounceActivityPubUrl(video.url, actor)
46 const announceActivity = await announceActivityData(url, account, addActivity, undefined) 46 const announceActivity = await announceActivityData(url, actor, createActivity, undefined)
47 47
48 activities.push(announceActivity) 48 activities.push(announceActivity)
49 } else { 49 } else {
50 const addActivity = await addActivityData(video.url, account, video, videoChannel.Actor.url, videoObject, undefined) 50 const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
51 51
52 activities.push(addActivity) 52 activities.push(createActivity)
53 } 53 }
54 } 54 }
55 55
@@ -57,7 +57,7 @@ async function outboxController (req: express.Request, res: express.Response, ne
57 data: activities, 57 data: activities,
58 total: data.total 58 total: data.total
59 } 59 }
60 const json = activityPubCollectionPagination(account.url + '/outbox', page, newResult) 60 const json = activityPubCollectionPagination(account.Actor.url + '/outbox', page, newResult)
61 61
62 return res.json(json).end() 62 return res.json(json).end()
63} 63}
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 497edb8eb..e7d81f7c3 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -1,10 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers' 3import { getFormattedObjects, getServerActor, loadActorUrlOrGetFromWebfinger, logger, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers' 4import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers'
5import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub' 5import { getOrCreateActorAndServerAndModel } from '../../../lib/activitypub'
6import { sendUndoFollow } from '../../../lib/activitypub/send' 6import { sendFollow, sendUndoFollow } from '../../../lib/activitypub/send'
7import { sendFollow } from '../../../lib/index'
8import { 7import {
9 asyncMiddleware, 8 asyncMiddleware,
10 authenticate, 9 authenticate,
@@ -17,8 +16,8 @@ import {
17 setPagination 16 setPagination
18} from '../../../middlewares' 17} from '../../../middlewares'
19import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' 18import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators'
20import { AccountModel } from '../../../models/account/account' 19import { ActorModel } from '../../../models/activitypub/actor'
21import { AccountFollowModel } from '../../../models/account/account-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
22 21
23const serverFollowsRouter = express.Router() 22const serverFollowsRouter = express.Router()
24 23
@@ -38,7 +37,7 @@ serverFollowsRouter.post('/following',
38 asyncMiddleware(followRetry) 37 asyncMiddleware(followRetry)
39) 38)
40 39
41serverFollowsRouter.delete('/following/:accountId', 40serverFollowsRouter.delete('/following/:host',
42 authenticate, 41 authenticate,
43 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), 42 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
44 asyncMiddleware(removeFollowingValidator), 43 asyncMiddleware(removeFollowingValidator),
@@ -62,43 +61,41 @@ export {
62// --------------------------------------------------------------------------- 61// ---------------------------------------------------------------------------
63 62
64async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 63async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
65 const serverAccount = await getServerAccount() 64 const serverActor = await getServerActor()
66 const resultList = await AccountFollowModel.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) 65 const resultList = await ActorFollowModel.listFollowingForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
67 66
68 return res.json(getFormattedObjects(resultList.data, resultList.total)) 67 return res.json(getFormattedObjects(resultList.data, resultList.total))
69} 68}
70 69
71async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 70async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
72 const serverAccount = await getServerAccount() 71 const serverActor = await getServerActor()
73 const resultList = await AccountFollowModel.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) 72 const resultList = await ActorFollowModel.listFollowersForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
74 73
75 return res.json(getFormattedObjects(resultList.data, resultList.total)) 74 return res.json(getFormattedObjects(resultList.data, resultList.total))
76} 75}
77 76
78async function followRetry (req: express.Request, res: express.Response, next: express.NextFunction) { 77async function followRetry (req: express.Request, res: express.Response, next: express.NextFunction) {
79 const hosts = req.body.hosts as string[] 78 const hosts = req.body.hosts as string[]
80 const fromAccount = await getServerAccount() 79 const fromActor = await getServerActor()
81 80
82 const tasks: Promise<any>[] = [] 81 const tasks: Promise<any>[] = []
83 const accountName = SERVER_ACCOUNT_NAME 82 const actorName = SERVER_ACTOR_NAME
84 83
85 for (const host of hosts) { 84 for (const host of hosts) {
86
87 // We process each host in a specific transaction 85 // We process each host in a specific transaction
88 // First, we add the follow request in the database 86 // First, we add the follow request in the database
89 // Then we send the follow request to other account 87 // Then we send the follow request to other actor
90 const p = loadLocalOrGetAccountFromWebfinger(accountName, host) 88 const p = loadActorUrlOrGetFromWebfinger(actorName, host)
91 .then(accountResult => { 89 .then(actorUrl => getOrCreateActorAndServerAndModel(actorUrl))
92 let targetAccount = accountResult.account 90 .then(targetActor => {
93
94 const options = { 91 const options = {
95 arguments: [ fromAccount, targetAccount, accountResult.loadedFromDB ], 92 arguments: [ fromActor, targetActor ],
96 errorMessage: 'Cannot follow with many retries.' 93 errorMessage: 'Cannot follow with many retries.'
97 } 94 }
98 95
99 return retryTransactionWrapper(follow, options) 96 return retryTransactionWrapper(follow, options)
100 }) 97 })
101 .catch(err => logger.warn('Cannot follow server %s.', `${accountName}@${host}`, err)) 98 .catch(err => logger.warn('Cannot follow server %s.', host, err))
102 99
103 tasks.push(p) 100 tasks.push(p)
104 } 101 }
@@ -110,42 +107,32 @@ async function followRetry (req: express.Request, res: express.Response, next: e
110 return res.status(204).end() 107 return res.status(204).end()
111} 108}
112 109
113async function follow (fromAccount: AccountModel, targetAccount: AccountModel, targetAlreadyInDB: boolean) { 110function follow (fromActor: ActorModel, targetActor: ActorModel) {
114 try { 111 return sequelizeTypescript.transaction(async t => {
115 await sequelizeTypescript.transaction(async t => { 112 const [ actorFollow ] = await ActorFollowModel.findOrCreate({
116 if (targetAlreadyInDB === false) { 113 where: {
117 await saveAccountAndServerIfNotExist(targetAccount, t) 114 actorId: fromActor.id,
118 } 115 targetActorId: targetActor.id
119 116 },
120 const [ accountFollow ] = await AccountFollowModel.findOrCreate({ 117 defaults: {
121 where: { 118 state: 'pending',
122 accountId: fromAccount.id, 119 actorId: fromActor.id,
123 targetAccountId: targetAccount.id 120 targetActorId: targetActor.id
124 }, 121 },
125 defaults: { 122 transaction: t
126 state: 'pending',
127 accountId: fromAccount.id,
128 targetAccountId: targetAccount.id
129 },
130 transaction: t
131 })
132 accountFollow.AccountFollowing = targetAccount
133 accountFollow.AccountFollower = fromAccount
134
135 // Send a notification to remote server
136 if (accountFollow.state === 'pending') {
137 await sendFollow(accountFollow, t)
138 }
139 }) 123 })
140 } catch (err) { 124 actorFollow.ActorFollowing = targetActor
141 // Reset target account 125 actorFollow.ActorFollower = fromActor
142 targetAccount.isNewRecord = !targetAlreadyInDB 126
143 throw err 127 // Send a notification to remote server
144 } 128 if (actorFollow.state === 'pending') {
129 await sendFollow(actorFollow, t)
130 }
131 })
145} 132}
146 133
147async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { 134async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) {
148 const follow: AccountFollowModel = res.locals.follow 135 const follow: ActorFollowModel = res.locals.follow
149 136
150 await sequelizeTypescript.transaction(async t => { 137 await sequelizeTypescript.transaction(async t => {
151 if (follow.state === 'accepted') await sendUndoFollow(follow, t) 138 if (follow.state === 'accepted') await sendUndoFollow(follow, t)
@@ -153,24 +140,11 @@ async function removeFollow (req: express.Request, res: express.Response, next:
153 await follow.destroy({ transaction: t }) 140 await follow.destroy({ transaction: t })
154 }) 141 })
155 142
156 // Destroy the account that will destroy video channels, videos and video files too 143 // Destroy the actor that will destroy video channels, videos and video files too
157 // This could be long so don't wait this task 144 // This could be long so don't wait this task
158 const following = follow.AccountFollowing 145 const following = follow.ActorFollowing
159 following.destroy() 146 following.destroy()
160 .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.Actor.url, err)) 147 .catch(err => logger.error('Cannot destroy actor that we do not follow anymore %s.', following.url, err))
161 148
162 return res.status(204).end() 149 return res.status(204).end()
163} 150}
164
165async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
166 let loadedFromDB = true
167 let account = await AccountModel.loadByNameAndHost(name, host)
168
169 if (!account) {
170 const nameWithDomain = name + '@' + host
171 account = await getAccountFromWebfinger(nameWithDomain)
172 loadedFromDB = false
173 }
174
175 return { account, loadedFromDB }
176}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 995542604..3106df9b9 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' 2import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
3import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' 3import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers'
4import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers'
5import { createUserAccountAndChannel } from '../../lib' 5import { createUserAccountAndChannel } from '../../lib/user'
6import { 6import {
7 asyncMiddleware, 7 asyncMiddleware,
8 authenticate, 8 authenticate,
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 08cc4d0b4..fecdaf5a3 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -1,22 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { 2import { UserRight, VideoAbuseCreate } from '../../../../shared'
3 logger, 3import { getFormattedObjects, logger, retryTransactionWrapper } from '../../../helpers'
4 getFormattedObjects,
5 retryTransactionWrapper
6} from '../../../helpers'
7import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { sendVideoAbuse } from '../../../lib/activitypub/send'
8import { 6import {
7 asyncMiddleware,
9 authenticate, 8 authenticate,
10 ensureUserHasRight, 9 ensureUserHasRight,
11 paginationValidator, 10 paginationValidator,
12 videoAbuseReportValidator,
13 videoAbusesSortValidator,
14 setVideoAbusesSort,
15 setPagination, 11 setPagination,
16 asyncMiddleware 12 setVideoAbusesSort,
13 videoAbuseReportValidator,
14 videoAbusesSortValidator
17} from '../../../middlewares' 15} from '../../../middlewares'
18import { VideoAbuseCreate, UserRight } from '../../../../shared'
19import { sendVideoAbuse } from '../../../lib/index'
20import { AccountModel } from '../../../models/account/account' 16import { AccountModel } from '../../../models/account/account'
21import { VideoModel } from '../../../models/video/video' 17import { VideoModel } from '../../../models/video/video'
22import { VideoAbuseModel } from '../../../models/video/video-abuse' 18import { VideoAbuseModel } from '../../../models/video/video-abuse'
@@ -80,7 +76,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
80 76
81 // We send the video abuse to the origin server 77 // We send the video abuse to the origin server
82 if (videoInstance.isOwned() === false) { 78 if (videoInstance.isOwned() === false) {
83 await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t) 79 await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
84 } 80 }
85 }) 81 })
86 82
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
index 315469115..cc00d9f8d 100644
--- a/server/controllers/api/videos/channel.ts
+++ b/server/controllers/api/videos/channel.ts
@@ -2,8 +2,8 @@ import * as express from 'express'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' 2import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
3import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' 3import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { createVideoChannel } from '../../../lib' 5import { setAsyncActorKeys } from '../../../lib/activitypub'
6import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' 6import { createVideoChannel } from '../../../lib/video-channel'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
9 authenticate, 9 authenticate,
@@ -92,15 +92,17 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R
92 return res.type('json').status(204).end() 92 return res.type('json').status(204).end()
93} 93}
94 94
95function addVideoChannel (req: express.Request, res: express.Response) { 95async function addVideoChannel (req: express.Request, res: express.Response) {
96 const videoChannelInfo: VideoChannelCreate = req.body 96 const videoChannelInfo: VideoChannelCreate = req.body
97 const account: AccountModel = res.locals.oauth.token.User.Account 97 const account: AccountModel = res.locals.oauth.token.User.Account
98 98
99 return sequelizeTypescript.transaction(async t => { 99 const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
100 const videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t) 100 return createVideoChannel(videoChannelInfo, account, t)
101
102 logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
103 }) 101 })
102
103 setAsyncActorKeys(videoChannelCreated.Actor)
104
105 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
104} 106}
105 107
106async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 108async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -128,12 +130,13 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
128 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) 130 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
129 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) 131 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
130 132
131 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) 133 await videoChannelInstance.save(sequelizeOptions)
132 134
133 await sendUpdateVideoChannel(videoChannelInstanceUpdated, t) 135 // TODO
136 // await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
134 }) 137 })
135 138
136 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid) 139 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
137 } catch (err) { 140 } catch (err) {
138 logger.debug('Cannot update the video channel.', err) 141 logger.debug('Cannot update the video channel.', err)
139 142
@@ -160,11 +163,12 @@ async function removeVideoChannelRetryWrapper (req: express.Request, res: expres
160async function removeVideoChannel (req: express.Request, res: express.Response) { 163async function removeVideoChannel (req: express.Request, res: express.Response) {
161 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 164 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
162 165
163 await sequelizeTypescript.transaction(async t => { 166 return sequelizeTypescript.transaction(async t => {
164 await videoChannelInstance.destroy({ transaction: t }) 167 await videoChannelInstance.destroy({ transaction: t })
168
169 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
165 }) 170 })
166 171
167 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
168} 172}
169 173
170async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 174async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 91ab8c66a..d6934748f 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -11,7 +11,7 @@ import {
11 resetSequelizeInstance, 11 resetSequelizeInstance,
12 retryTransactionWrapper 12 retryTransactionWrapper
13} from '../../../helpers' 13} from '../../../helpers'
14import { getServerAccount } from '../../../helpers/utils' 14import { getServerActor } from '../../../helpers/utils'
15import { 15import {
16 CONFIG, 16 CONFIG,
17 sequelizeTypescript, 17 sequelizeTypescript,
@@ -22,8 +22,7 @@ import {
22 VIDEO_PRIVACIES 22 VIDEO_PRIVACIES
23} from '../../../initializers' 23} from '../../../initializers'
24import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' 24import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub'
25import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send' 25import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
26import { sendCreateViewToVideoFollowers } from '../../../lib/index'
27import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' 26import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler'
28import { 27import {
29 asyncMiddleware, 28 asyncMiddleware,
@@ -248,7 +247,8 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
248 // Don't send video to remote servers, it is private 247 // Don't send video to remote servers, it is private
249 if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated 248 if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated
250 249
251 await sendAddVideo(video, t) 250 await sendCreateVideo(video, t)
251 // TODO: share by video channel
252 await shareVideoByServer(video, t) 252 await shareVideoByServer(video, t)
253 253
254 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) 254 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) {
304 304
305 // Video is not private anymore, send a create action to remote servers 305 // Video is not private anymore, send a create action to remote servers
306 if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) { 306 if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
307 await sendAddVideo(videoInstanceUpdated, t) 307 await sendCreateVideo(videoInstanceUpdated, t)
308 // TODO: Send by video channel
308 await shareVideoByServer(videoInstanceUpdated, t) 309 await shareVideoByServer(videoInstanceUpdated, t)
309 } 310 }
310 }) 311 })
@@ -330,7 +331,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
330 const videoInstance = res.locals.video 331 const videoInstance = res.locals.video
331 332
332 await videoInstance.increment('views') 333 await videoInstance.increment('views')
333 const serverAccount = await getServerAccount() 334 const serverAccount = await getServerActor()
334 335
335 if (videoInstance.isOwned()) { 336 if (videoInstance.isOwned()) {
336 await sendCreateViewToVideoFollowers(serverAccount, videoInstance, undefined) 337 await sendCreateViewToVideoFollowers(serverAccount, videoInstance, undefined)
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 33aed8927..ccae60517 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -1,11 +1,7 @@
1import * as express from 'express'
2import * as cors from 'cors' 1import * as cors from 'cors'
3import { 2import * as express from 'express'
4 CONFIG, 3import { CONFIG, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers'
5 STATIC_MAX_AGE, 4import { VideosPreviewCache } from '../lib/cache'
6 STATIC_PATHS
7} from '../initializers'
8import { VideosPreviewCache } from '../lib'
9import { asyncMiddleware } from '../middlewares' 5import { asyncMiddleware } from '../middlewares'
10 6
11const staticRouter = express.Router() 7const staticRouter = express.Router()
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index 8829500bc..ed781c21b 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares' 2import { asyncMiddleware } from '../middlewares'
3import { webfingerValidator } from '../middlewares/validators' 3import { webfingerValidator } from '../middlewares/validators'
4import { AccountModel } from '../models/account/account' 4import { ActorModel } from '../models/activitypub/actor'
5 5
6const webfingerRouter = express.Router() 6const webfingerRouter = express.Router()
7 7
@@ -19,16 +19,16 @@ export {
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { 21function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) {
22 const account = res.locals.account as AccountModel 22 const actor = res.locals.actor as ActorModel
23 23
24 const json = { 24 const json = {
25 subject: req.query.resource, 25 subject: req.query.resource,
26 aliases: [ account.Actor.url ], 26 aliases: [ actor.url ],
27 links: [ 27 links: [
28 { 28 {
29 rel: 'self', 29 rel: 'self',
30 type: 'application/activity+json', 30 type: 'application/activity+json',
31 href: account.Actor.url 31 href: actor.url
32 } 32 }
33 ] 33 ]
34 } 34 }
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 43907b596..5850fc19f 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -1,7 +1,7 @@
1import { ResultList } from '../../shared/models' 1import { ResultList } from '../../shared/models'
2import { Activity } from '../../shared/models/activitypub' 2import { Activity } from '../../shared/models/activitypub'
3import { ACTIVITY_PUB } from '../initializers' 3import { ACTIVITY_PUB } from '../initializers'
4import { AccountModel } from '../models/account/account' 4import { ActorModel } from '../models/activitypub/actor'
5import { signObject } from './peertube-crypto' 5import { signObject } from './peertube-crypto'
6 6
7function activityPubContextify <T> (data: T) { 7function activityPubContextify <T> (data: T) {
@@ -71,10 +71,10 @@ function activityPubCollectionPagination (url: string, page: any, result: Result
71 return orderedCollectionPagination 71 return orderedCollectionPagination
72} 72}
73 73
74function buildSignedActivity (byAccount: AccountModel, data: Object) { 74function buildSignedActivity (byActor: ActorModel, data: Object) {
75 const activity = activityPubContextify(data) 75 const activity = activityPubContextify(data)
76 76
77 return signObject(byAccount, activity) as Promise<Activity> 77 return signObject(byActor, activity) as Promise<Activity>
78} 78}
79 79
80// --------------------------------------------------------------------------- 80// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts
index ae7732194..c402800a4 100644
--- a/server/helpers/custom-validators/activitypub/activity.ts
+++ b/server/helpers/custom-validators/activitypub/activity.ts
@@ -1,14 +1,14 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { Activity, ActivityType } from '../../../../shared/models/activitypub' 2import { Activity, ActivityType } from '../../../../shared/models/activitypub'
3import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './actor' 3import { isActorAcceptActivityValid, isActorDeleteActivityValid, isActorFollowActivityValid } from './actor'
4import { isAnnounceActivityValid } from './announce' 4import { isAnnounceActivityValid } from './announce'
5import { isActivityPubUrlValid } from './misc' 5import { isActivityPubUrlValid } from './misc'
6import { isDislikeActivityValid, isLikeActivityValid } from './rate' 6import { isDislikeActivityValid, isLikeActivityValid } from './rate'
7import { isUndoActivityValid } from './undo' 7import { isUndoActivityValid } from './undo'
8import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' 8import { isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
9import { 9import {
10 isVideoFlagValid, 10 isVideoFlagValid,
11 isVideoTorrentAddActivityValid, 11 isVideoTorrentCreateActivityValid,
12 isVideoTorrentDeleteActivityValid, 12 isVideoTorrentDeleteActivityValid,
13 isVideoTorrentUpdateActivityValid 13 isVideoTorrentUpdateActivityValid
14} from './videos' 14} from './videos'
@@ -29,7 +29,6 @@ function isRootActivityValid (activity: any) {
29 29
30const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = { 30const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
31 Create: checkCreateActivity, 31 Create: checkCreateActivity,
32 Add: checkAddActivity,
33 Update: checkUpdateActivity, 32 Update: checkUpdateActivity,
34 Delete: checkDeleteActivity, 33 Delete: checkDeleteActivity,
35 Follow: checkFollowActivity, 34 Follow: checkFollowActivity,
@@ -59,14 +58,10 @@ export {
59function checkCreateActivity (activity: any) { 58function checkCreateActivity (activity: any) {
60 return isViewActivityValid(activity) || 59 return isViewActivityValid(activity) ||
61 isDislikeActivityValid(activity) || 60 isDislikeActivityValid(activity) ||
62 isVideoChannelCreateActivityValid(activity) || 61 isVideoTorrentCreateActivityValid(activity) ||
63 isVideoFlagValid(activity) 62 isVideoFlagValid(activity)
64} 63}
65 64
66function checkAddActivity (activity: any) {
67 return isVideoTorrentAddActivityValid(activity)
68}
69
70function checkUpdateActivity (activity: any) { 65function checkUpdateActivity (activity: any) {
71 return isVideoTorrentUpdateActivityValid(activity) || 66 return isVideoTorrentUpdateActivityValid(activity) ||
72 isVideoChannelUpdateActivityValid(activity) 67 isVideoChannelUpdateActivityValid(activity)
@@ -75,15 +70,15 @@ function checkUpdateActivity (activity: any) {
75function checkDeleteActivity (activity: any) { 70function checkDeleteActivity (activity: any) {
76 return isVideoTorrentDeleteActivityValid(activity) || 71 return isVideoTorrentDeleteActivityValid(activity) ||
77 isVideoChannelDeleteActivityValid(activity) || 72 isVideoChannelDeleteActivityValid(activity) ||
78 isAccountDeleteActivityValid(activity) 73 isActorDeleteActivityValid(activity)
79} 74}
80 75
81function checkFollowActivity (activity: any) { 76function checkFollowActivity (activity: any) {
82 return isAccountFollowActivityValid(activity) 77 return isActorFollowActivityValid(activity)
83} 78}
84 79
85function checkAcceptActivity (activity: any) { 80function checkAcceptActivity (activity: any) {
86 return isAccountAcceptActivityValid(activity) 81 return isActorAcceptActivityValid(activity)
87} 82}
88 83
89function checkAnnounceActivity (activity: any) { 84function checkAnnounceActivity (activity: any) {
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts
index 28551c96c..bf42757c5 100644
--- a/server/helpers/custom-validators/activitypub/actor.ts
+++ b/server/helpers/custom-validators/activitypub/actor.ts
@@ -1,8 +1,12 @@
1import * as Bluebird from 'bluebird'
2import { Response } from 'express'
1import * as validator from 'validator' 3import * as validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../../initializers' 4import { CONSTRAINTS_FIELDS } from '../../../initializers'
5import { ActorModel } from '../../../models/activitypub/actor'
3import { isAccountNameValid } from '../accounts' 6import { isAccountNameValid } from '../accounts'
4import { exists, isUUIDValid } from '../misc' 7import { exists, isUUIDValid } from '../misc'
5import { isActivityPubUrlValid, isBaseActivityValid } from './misc' 8import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
9import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
6 10
7function isActorEndpointsObjectValid (endpointObject: any) { 11function isActorEndpointsObjectValid (endpointObject: any) {
8 return isActivityPubUrlValid(endpointObject.sharedInbox) 12 return isActivityPubUrlValid(endpointObject.sharedInbox)
@@ -27,7 +31,12 @@ function isActorPublicKeyValid (publicKey: string) {
27} 31}
28 32
29function isActorPreferredUsernameValid (preferredUsername: string) { 33function isActorPreferredUsernameValid (preferredUsername: string) {
30 return isAccountNameValid(preferredUsername) 34 return isAccountNameValid(preferredUsername) || isVideoChannelNameValid(preferredUsername)
35}
36
37const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
38function isActorNameValid (name: string) {
39 return exists(name) && validator.matches(name, actorNameRegExp)
31} 40}
32 41
33function isActorPrivateKeyValid (privateKey: string) { 42function isActorPrivateKeyValid (privateKey: string) {
@@ -46,10 +55,16 @@ function isRemoteActorValid (remoteActor: any) {
46 isActivityPubUrlValid(remoteActor.followers) && 55 isActivityPubUrlValid(remoteActor.followers) &&
47 isActivityPubUrlValid(remoteActor.inbox) && 56 isActivityPubUrlValid(remoteActor.inbox) &&
48 isActivityPubUrlValid(remoteActor.outbox) && 57 isActivityPubUrlValid(remoteActor.outbox) &&
58 isActorNameValid(remoteActor.name) &&
49 isActorPreferredUsernameValid(remoteActor.preferredUsername) && 59 isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
50 isActivityPubUrlValid(remoteActor.url) && 60 isActivityPubUrlValid(remoteActor.url) &&
51 isActorPublicKeyObjectValid(remoteActor.publicKey) && 61 isActorPublicKeyObjectValid(remoteActor.publicKey) &&
52 isActorEndpointsObjectValid(remoteActor.endpoints) 62 isActorEndpointsObjectValid(remoteActor.endpoints) &&
63 (!remoteActor.summary || isVideoChannelDescriptionValid(remoteActor.summary)) &&
64 setValidAttributedTo(remoteActor) &&
65 // If this is not an account, it should be attributed to an account
66 // In PeerTube we use this to attach a video channel to a specific account
67 (remoteActor.type === 'Person' || remoteActor.attributedTo.length !== 0)
53} 68}
54 69
55function isActorFollowingCountValid (value: string) { 70function isActorFollowingCountValid (value: string) {
@@ -73,6 +88,40 @@ function isActorAcceptActivityValid (activity: any) {
73 return isBaseActivityValid(activity, 'Accept') 88 return isBaseActivityValid(activity, 'Accept')
74} 89}
75 90
91function isActorIdExist (id: number | string, res: Response) {
92 let promise: Bluebird<ActorModel>
93
94 if (validator.isInt('' + id)) {
95 promise = ActorModel.load(+id)
96 } else { // UUID
97 promise = ActorModel.loadByUUID('' + id)
98 }
99
100 return isActorExist(promise, res)
101}
102
103function isLocalActorNameExist (name: string, res: Response) {
104 const promise = ActorModel.loadLocalByName(name)
105
106 return isActorExist(promise, res)
107}
108
109async function isActorExist (p: Bluebird<ActorModel>, res: Response) {
110 const actor = await p
111
112 if (!actor) {
113 res.status(404)
114 .send({ error: 'Actor not found' })
115 .end()
116
117 return false
118 }
119
120 res.locals.actor = actor
121
122 return true
123}
124
76// --------------------------------------------------------------------------- 125// ---------------------------------------------------------------------------
77 126
78export { 127export {
@@ -87,5 +136,9 @@ export {
87 isActorFollowersCountValid, 136 isActorFollowersCountValid,
88 isActorFollowActivityValid, 137 isActorFollowActivityValid,
89 isActorAcceptActivityValid, 138 isActorAcceptActivityValid,
90 isActorDeleteActivityValid 139 isActorDeleteActivityValid,
140 isActorIdExist,
141 isLocalActorNameExist,
142 isActorNameValid,
143 isActorExist
91} 144}
diff --git a/server/helpers/custom-validators/activitypub/announce.ts b/server/helpers/custom-validators/activitypub/announce.ts
index 45f6b05a0..80511129c 100644
--- a/server/helpers/custom-validators/activitypub/announce.ts
+++ b/server/helpers/custom-validators/activitypub/announce.ts
@@ -1,12 +1,10 @@
1import { isBaseActivityValid } from './misc' 1import { isBaseActivityValid } from './misc'
2import { isVideoTorrentAddActivityValid } from './videos' 2import { isVideoTorrentCreateActivityValid } from './videos'
3import { isVideoChannelCreateActivityValid } from './video-channels'
4 3
5function isAnnounceActivityValid (activity: any) { 4function isAnnounceActivityValid (activity: any) {
6 return isBaseActivityValid(activity, 'Announce') && 5 return isBaseActivityValid(activity, 'Announce') &&
7 ( 6 (
8 isVideoChannelCreateActivityValid(activity.object) || 7 isVideoTorrentCreateActivityValid(activity.object)
9 isVideoTorrentAddActivityValid(activity.object)
10 ) 8 )
11} 9}
12 10
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts
index 65f5ca809..3ca4e4ff4 100644
--- a/server/helpers/custom-validators/activitypub/misc.ts
+++ b/server/helpers/custom-validators/activitypub/misc.ts
@@ -17,7 +17,7 @@ function isActivityPubUrlValid (url: string) {
17 isURLOptions.require_tld = false 17 isURLOptions.require_tld = false
18 } 18 }
19 19
20 return exists(url) && validator.isURL(url, isURLOptions) && validator.isLength(url, CONSTRAINTS_FIELDS.ACCOUNTS.URL) 20 return exists(url) && validator.isURL(url, isURLOptions) && validator.isLength(url, CONSTRAINTS_FIELDS.ACTOR.URL)
21} 21}
22 22
23function isBaseActivityValid (activity: any, type: string) { 23function isBaseActivityValid (activity: any, type: string) {
@@ -35,7 +35,23 @@ function isBaseActivityValid (activity: any, type: string) {
35 ) 35 )
36} 36}
37 37
38function setValidAttributedTo (obj: any) {
39 if (Array.isArray(obj.attributedTo) === false) {
40 obj.attributedTo = []
41 return true
42 }
43
44 const newAttributesTo = obj.attributedTo.filter(a => {
45 return (a.type === 'Group' || a.type === 'Person') && isActivityPubUrlValid(a.id)
46 })
47
48 obj.attributedTo = newAttributesTo
49
50 return true
51}
52
38export { 53export {
39 isActivityPubUrlValid, 54 isActivityPubUrlValid,
40 isBaseActivityValid 55 isBaseActivityValid,
56 setValidAttributedTo
41} 57}
diff --git a/server/helpers/custom-validators/activitypub/undo.ts b/server/helpers/custom-validators/activitypub/undo.ts
index d07bbf6b7..a2831b0bf 100644
--- a/server/helpers/custom-validators/activitypub/undo.ts
+++ b/server/helpers/custom-validators/activitypub/undo.ts
@@ -1,11 +1,11 @@
1import { isAccountFollowActivityValid } from './actor' 1import { isActorFollowActivityValid } from './actor'
2import { isBaseActivityValid } from './misc' 2import { isBaseActivityValid } from './misc'
3import { isDislikeActivityValid, isLikeActivityValid } from './rate' 3import { isDislikeActivityValid, isLikeActivityValid } from './rate'
4 4
5function isUndoActivityValid (activity: any) { 5function isUndoActivityValid (activity: any) {
6 return isBaseActivityValid(activity, 'Undo') && 6 return isBaseActivityValid(activity, 'Undo') &&
7 ( 7 (
8 isAccountFollowActivityValid(activity.object) || 8 isActorFollowActivityValid(activity.object) ||
9 isLikeActivityValid(activity.object) || 9 isLikeActivityValid(activity.object) ||
10 isDislikeActivityValid(activity.object) 10 isDislikeActivityValid(activity.object)
11 ) 11 )
diff --git a/server/helpers/custom-validators/activitypub/video-channels.ts b/server/helpers/custom-validators/activitypub/video-channels.ts
index 9fd3bb149..eb45c6372 100644
--- a/server/helpers/custom-validators/activitypub/video-channels.ts
+++ b/server/helpers/custom-validators/activitypub/video-channels.ts
@@ -2,11 +2,6 @@ import { isDateValid, isUUIDValid } from '../misc'
2import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' 2import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
3import { isActivityPubUrlValid, isBaseActivityValid } from './misc' 3import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
4 4
5function isVideoChannelCreateActivityValid (activity: any) {
6 return isBaseActivityValid(activity, 'Create') &&
7 isVideoChannelObjectValid(activity.object)
8}
9
10function isVideoChannelUpdateActivityValid (activity: any) { 5function isVideoChannelUpdateActivityValid (activity: any) {
11 return isBaseActivityValid(activity, 'Update') && 6 return isBaseActivityValid(activity, 'Update') &&
12 isVideoChannelObjectValid(activity.object) 7 isVideoChannelObjectValid(activity.object)
@@ -29,7 +24,6 @@ function isVideoChannelObjectValid (videoChannel: any) {
29// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
30 25
31export { 26export {
32 isVideoChannelCreateActivityValid,
33 isVideoChannelUpdateActivityValid, 27 isVideoChannelUpdateActivityValid,
34 isVideoChannelDeleteActivityValid, 28 isVideoChannelDeleteActivityValid,
35 isVideoChannelObjectValid 29 isVideoChannelObjectValid
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index 2ed2988f5..b485e5fcf 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -10,10 +10,10 @@ import {
10 isVideoTruncatedDescriptionValid, 10 isVideoTruncatedDescriptionValid,
11 isVideoViewsValid 11 isVideoViewsValid
12} from '../videos' 12} from '../videos'
13import { isActivityPubUrlValid, isBaseActivityValid } from './misc' 13import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
14 14
15function isVideoTorrentAddActivityValid (activity: any) { 15function isVideoTorrentCreateActivityValid (activity: any) {
16 return isBaseActivityValid(activity, 'Add') && 16 return isBaseActivityValid(activity, 'Create') &&
17 isVideoTorrentObjectValid(activity.object) 17 isVideoTorrentObjectValid(activity.object)
18} 18}
19 19
@@ -43,6 +43,8 @@ function isActivityPubVideoDurationValid (value: string) {
43} 43}
44 44
45function isVideoTorrentObjectValid (video: any) { 45function isVideoTorrentObjectValid (video: any) {
46 console.log(video)
47
46 return video.type === 'Video' && 48 return video.type === 'Video' &&
47 isActivityPubUrlValid(video.id) && 49 isActivityPubUrlValid(video.id) &&
48 isVideoNameValid(video.name) && 50 isVideoNameValid(video.name) &&
@@ -59,13 +61,15 @@ function isVideoTorrentObjectValid (video: any) {
59 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && 61 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
60 isRemoteVideoIconValid(video.icon) && 62 isRemoteVideoIconValid(video.icon) &&
61 setValidRemoteVideoUrls(video) && 63 setValidRemoteVideoUrls(video) &&
62 video.url.length !== 0 64 video.url.length !== 0 &&
65 setValidAttributedTo(video) &&
66 video.attributedTo.length !== 0
63} 67}
64 68
65// --------------------------------------------------------------------------- 69// ---------------------------------------------------------------------------
66 70
67export { 71export {
68 isVideoTorrentAddActivityValid, 72 isVideoTorrentCreateActivityValid,
69 isVideoTorrentUpdateActivityValid, 73 isVideoTorrentUpdateActivityValid,
70 isVideoTorrentDeleteActivityValid, 74 isVideoTorrentDeleteActivityValid,
71 isVideoFlagValid 75 isVideoFlagValid
diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts
index 38f6b938d..c53db4027 100644
--- a/server/helpers/custom-validators/webfinger.ts
+++ b/server/helpers/custom-validators/webfinger.ts
@@ -5,11 +5,11 @@ function isWebfingerResourceValid (value: string) {
5 if (!exists(value)) return false 5 if (!exists(value)) return false
6 if (value.startsWith('acct:') === false) return false 6 if (value.startsWith('acct:') === false) return false
7 7
8 const accountWithHost = value.substr(5) 8 const actorWithHost = value.substr(5)
9 const accountParts = accountWithHost.split('@') 9 const actorParts = actorWithHost.split('@')
10 if (accountParts.length !== 2) return false 10 if (actorParts.length !== 2) return false
11 11
12 const host = accountParts[1] 12 const host = actorParts[1]
13 13
14 return host === CONFIG.WEBSERVER.HOST 14 return host === CONFIG.WEBSERVER.HOST
15} 15}
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index c4c735cb8..a0c9112b9 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,5 +1,5 @@
1import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' 1import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers'
2import { AccountModel } from '../models/account/account' 2import { ActorModel } from '../models/activitypub/actor'
3import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' 3import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils'
4import { jsig } from './custom-jsonld-signature' 4import { jsig } from './custom-jsonld-signature'
5import { logger } from './logger' 5import { logger } from './logger'
@@ -13,18 +13,18 @@ async function createPrivateAndPublicKeys () {
13 return { privateKey: key, publicKey } 13 return { privateKey: key, publicKey }
14} 14}
15 15
16function isSignatureVerified (fromAccount: AccountModel, signedDocument: object) { 16function isSignatureVerified (fromActor: ActorModel, signedDocument: object) {
17 const publicKeyObject = { 17 const publicKeyObject = {
18 '@context': jsig.SECURITY_CONTEXT_URL, 18 '@context': jsig.SECURITY_CONTEXT_URL,
19 '@id': fromAccount.url, 19 '@id': fromActor.url,
20 '@type': 'CryptographicKey', 20 '@type': 'CryptographicKey',
21 owner: fromAccount.url, 21 owner: fromActor.url,
22 publicKeyPem: fromAccount.publicKey 22 publicKeyPem: fromActor.publicKey
23 } 23 }
24 24
25 const publicKeyOwnerObject = { 25 const publicKeyOwnerObject = {
26 '@context': jsig.SECURITY_CONTEXT_URL, 26 '@context': jsig.SECURITY_CONTEXT_URL,
27 '@id': fromAccount.url, 27 '@id': fromActor.url,
28 publicKey: [ publicKeyObject ] 28 publicKey: [ publicKeyObject ]
29 } 29 }
30 30
@@ -40,10 +40,10 @@ function isSignatureVerified (fromAccount: AccountModel, signedDocument: object)
40 }) 40 })
41} 41}
42 42
43function signObject (byAccount: AccountModel, data: any) { 43function signObject (byActor: ActorModel, data: any) {
44 const options = { 44 const options = {
45 privateKeyPem: byAccount.privateKey, 45 privateKeyPem: byActor.privateKey,
46 creator: byAccount.url 46 creator: byActor.url
47 } 47 }
48 48
49 return jsig.promises.sign(data, options) 49 return jsig.promises.sign(data, options)
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index cb5e536b8..ef6a878cf 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -3,8 +3,9 @@ import { Model } from 'sequelize-typescript'
3import { ResultList } from '../../shared' 3import { ResultList } from '../../shared'
4import { VideoResolution } from '../../shared/models/videos' 4import { VideoResolution } from '../../shared/models/videos'
5import { CONFIG } from '../initializers' 5import { CONFIG } from '../initializers'
6import { AccountModel } from '../models/account/account'
7import { UserModel } from '../models/account/user' 6import { UserModel } from '../models/account/user'
7import { ActorModel } from '../models/activitypub/actor'
8import { ApplicationModel } from '../models/application/application'
8import { pseudoRandomBytesPromise } from './core-utils' 9import { pseudoRandomBytesPromise } from './core-utils'
9import { logger } from './logger' 10import { logger } from './logger'
10 11
@@ -80,18 +81,19 @@ function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
80 }) 81 })
81} 82}
82 83
83let serverAccount: AccountModel 84let serverActor: ActorModel
84async function getServerAccount () { 85async function getServerActor () {
85 if (serverAccount === undefined) { 86 if (serverActor === undefined) {
86 serverAccount = await AccountModel.loadApplication() 87 const application = await ApplicationModel.load()
88 serverActor = application.Account.Actor
87 } 89 }
88 90
89 if (!serverAccount) { 91 if (!serverActor) {
90 logger.error('Cannot load server account.') 92 logger.error('Cannot load server actor.')
91 process.exit(0) 93 process.exit(0)
92 } 94 }
93 95
94 return Promise.resolve(serverAccount) 96 return Promise.resolve(serverActor)
95} 97}
96 98
97type SortType = { sortModel: any, sortValue: string } 99type SortType = { sortModel: any, sortValue: string }
@@ -105,6 +107,6 @@ export {
105 isSignupAllowed, 107 isSignupAllowed,
106 computeResolutionsToTranscode, 108 computeResolutionsToTranscode,
107 resetSequelizeInstance, 109 resetSequelizeInstance,
108 getServerAccount, 110 getServerActor,
109 SortType 111 SortType
110} 112}
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
index d98068cd7..76444fbe3 100644
--- a/server/helpers/webfinger.ts
+++ b/server/helpers/webfinger.ts
@@ -1,6 +1,6 @@
1import * as WebFinger from 'webfinger.js' 1import * as WebFinger from 'webfinger.js'
2import { WebFingerData } from '../../shared' 2import { WebFingerData } from '../../shared'
3import { fetchRemoteAccount } from '../lib/activitypub' 3import { ActorModel } from '../models/activitypub/actor'
4import { isTestInstance } from './core-utils' 4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators/activitypub' 5import { isActivityPubUrlValid } from './custom-validators/activitypub'
6 6
@@ -11,30 +11,33 @@ const webfinger = new WebFinger({
11 request_timeout: 3000 11 request_timeout: 3000
12}) 12})
13 13
14async function getAccountFromWebfinger (nameWithHost: string) { 14async function loadActorUrlOrGetFromWebfinger (name: string, host: string) {
15 const webfingerData: WebFingerData = await webfingerLookup(nameWithHost) 15 const actor = await ActorModel.loadByNameAndHost(name, host)
16 if (actor) return actor.url
16 17
17 if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.') 18 const webfingerData: WebFingerData = await webfingerLookup(name + '@' + host)
18 19 return getLinkOrThrow(webfingerData)
19 const selfLink = webfingerData.links.find(l => l.rel === 'self')
20 if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) {
21 throw new Error('Cannot find self link or href is not a valid URL.')
22 }
23
24 const account = await fetchRemoteAccount(selfLink.href)
25 if (account === undefined) throw new Error('Cannot fetch remote account ' + selfLink.href)
26
27 return account
28} 20}
29 21
30// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
31 23
32export { 24export {
33 getAccountFromWebfinger 25 loadActorUrlOrGetFromWebfinger
34} 26}
35 27
36// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
37 29
30function getLinkOrThrow (webfingerData: WebFingerData) {
31 if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.')
32
33 const selfLink = webfingerData.links.find(l => l.rel === 'self')
34 if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) {
35 throw new Error('Cannot find self link or href is not a valid URL.')
36 }
37
38 return selfLink.href
39}
40
38function webfingerLookup (nameWithHost: string) { 41function webfingerLookup (nameWithHost: string) {
39 return new Promise<WebFingerData>((res, rej) => { 42 return new Promise<WebFingerData>((res, rej) => {
40 webfinger.lookup(nameWithHost, (err, p) => { 43 webfinger.lookup(nameWithHost, (err, p) => {
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index f209bef90..04b610b7a 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,7 +1,8 @@
1import * as config from 'config' 1import * as config from 'config'
2import { join } from 'path' 2import { join } from 'path'
3import { JobCategory, JobState, VideoRateType } from '../../shared/models' 3import { JobCategory, JobState, VideoRateType } from '../../shared/models'
4import { FollowState } from '../../shared/models/accounts' 4import { FollowState } from '../../shared/models/actors'
5import { ActivityPubActorType } from '../../shared/models/activitypub'
5import { VideoPrivacy } from '../../shared/models/videos' 6import { VideoPrivacy } from '../../shared/models/videos'
6// Do not use barrels, remain constants as independent as possible 7// Do not use barrels, remain constants as independent as possible
7import { isTestInstance, root } from '../helpers/core-utils' 8import { isTestInstance, root } from '../helpers/core-utils'
@@ -210,7 +211,7 @@ const VIDEO_MIMETYPE_EXT = {
210 211
211// --------------------------------------------------------------------------- 212// ---------------------------------------------------------------------------
212 213
213const SERVER_ACCOUNT_NAME = 'peertube' 214const SERVER_ACTOR_NAME = 'peertube'
214 215
215const ACTIVITY_PUB = { 216const ACTIVITY_PUB = {
216 POTENTIAL_ACCEPT_HEADERS: [ 217 POTENTIAL_ACCEPT_HEADERS: [
@@ -229,6 +230,12 @@ const ACTIVITY_PUB = {
229 } 230 }
230} 231}
231 232
233const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
234 GROUP: 'Group',
235 PERSON: 'Person',
236 APPLICATION: 'Application'
237}
238
232// --------------------------------------------------------------------------- 239// ---------------------------------------------------------------------------
233 240
234// Number of points we add/remove from a friend after a successful/bad request 241// Number of points we add/remove from a friend after a successful/bad request
@@ -350,12 +357,13 @@ export {
350 REMOTE_SCHEME, 357 REMOTE_SCHEME,
351 FOLLOW_STATES, 358 FOLLOW_STATES,
352 AVATARS_DIR, 359 AVATARS_DIR,
353 SERVER_ACCOUNT_NAME, 360 SERVER_ACTOR_NAME,
354 PRIVATE_RSA_KEY_SIZE, 361 PRIVATE_RSA_KEY_SIZE,
355 SORTABLE_COLUMNS, 362 SORTABLE_COLUMNS,
356 STATIC_MAX_AGE, 363 STATIC_MAX_AGE,
357 STATIC_PATHS, 364 STATIC_PATHS,
358 ACTIVITY_PUB, 365 ACTIVITY_PUB,
366 ACTIVITY_PUB_ACTOR_TYPES,
359 THUMBNAILS_SIZE, 367 THUMBNAILS_SIZE,
360 VIDEO_CATEGORIES, 368 VIDEO_CATEGORIES,
361 VIDEO_LANGUAGES, 369 VIDEO_LANGUAGES,
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 85d205cdc..0b3f695f7 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -3,9 +3,10 @@ import { isTestInstance } from '../helpers/core-utils'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4 4
5import { AccountModel } from '../models/account/account' 5import { AccountModel } from '../models/account/account'
6import { AccountFollowModel } from '../models/account/account-follow'
7import { AccountVideoRateModel } from '../models/account/account-video-rate' 6import { AccountVideoRateModel } from '../models/account/account-video-rate'
8import { UserModel } from '../models/account/user' 7import { UserModel } from '../models/account/user'
8import { ActorModel } from '../models/activitypub/actor'
9import { ActorFollowModel } from '../models/activitypub/actor-follow'
9import { ApplicationModel } from '../models/application/application' 10import { ApplicationModel } from '../models/application/application'
10import { AvatarModel } from '../models/avatar/avatar' 11import { AvatarModel } from '../models/avatar/avatar'
11import { JobModel } from '../models/job/job' 12import { JobModel } from '../models/job/job'
@@ -17,7 +18,6 @@ import { VideoModel } from '../models/video/video'
17import { VideoAbuseModel } from '../models/video/video-abuse' 18import { VideoAbuseModel } from '../models/video/video-abuse'
18import { VideoBlacklistModel } from '../models/video/video-blacklist' 19import { VideoBlacklistModel } from '../models/video/video-blacklist'
19import { VideoChannelModel } from '../models/video/video-channel' 20import { VideoChannelModel } from '../models/video/video-channel'
20import { VideoChannelShareModel } from '../models/video/video-channel-share'
21import { VideoFileModel } from '../models/video/video-file' 21import { VideoFileModel } from '../models/video/video-file'
22import { VideoShareModel } from '../models/video/video-share' 22import { VideoShareModel } from '../models/video/video-share'
23import { VideoTagModel } from '../models/video/video-tag' 23import { VideoTagModel } from '../models/video/video-tag'
@@ -56,6 +56,8 @@ const sequelizeTypescript = new SequelizeTypescript({
56async function initDatabaseModels (silent: boolean) { 56async function initDatabaseModels (silent: boolean) {
57 sequelizeTypescript.addModels([ 57 sequelizeTypescript.addModels([
58 ApplicationModel, 58 ApplicationModel,
59 ActorModel,
60 ActorFollowModel,
59 AvatarModel, 61 AvatarModel,
60 AccountModel, 62 AccountModel,
61 JobModel, 63 JobModel,
@@ -64,11 +66,9 @@ async function initDatabaseModels (silent: boolean) {
64 ServerModel, 66 ServerModel,
65 TagModel, 67 TagModel,
66 AccountVideoRateModel, 68 AccountVideoRateModel,
67 AccountFollowModel,
68 UserModel, 69 UserModel,
69 VideoAbuseModel, 70 VideoAbuseModel,
70 VideoChannelModel, 71 VideoChannelModel,
71 VideoChannelShareModel,
72 VideoShareModel, 72 VideoShareModel,
73 VideoFileModel, 73 VideoFileModel,
74 VideoBlacklistModel, 74 VideoBlacklistModel,
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 5452743b6..ee3c9dfd9 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -1,12 +1,12 @@
1import * as passwordGenerator from 'password-generator' 1import * as passwordGenerator from 'password-generator'
2import { UserRole } from '../../shared' 2import { UserRole } from '../../shared'
3import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers' 3import { logger, mkdirpPromise, rimrafPromise } from '../helpers'
4import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib' 4import { createApplicationActor, createUserAccountAndChannel } from '../lib/user'
5import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/account/user'
6import { ApplicationModel } from '../models/application/application' 6import { ApplicationModel } from '../models/application/application'
7import { OAuthClientModel } from '../models/oauth/oauth-client' 7import { OAuthClientModel } from '../models/oauth/oauth-client'
8import { applicationExist, clientsExist, usersExist } from './checker' 8import { applicationExist, clientsExist, usersExist } from './checker'
9import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' 9import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants'
10import { sequelizeTypescript } from './database' 10import { sequelizeTypescript } from './database'
11 11
12async function installApplication () { 12async function installApplication () {
@@ -134,15 +134,12 @@ async function createApplicationIfNotExist () {
134 if (exist === true) return undefined 134 if (exist === true) return undefined
135 135
136 logger.info('Creating Application table.') 136 logger.info('Creating Application table.')
137 const applicationInstance = await ApplicationModel.create({ migrationVersion: LAST_MIGRATION_VERSION })
138 137
139 logger.info('Creating application account.') 138 logger.info('Creating application account.')
140 139
141 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined) 140 const application = await ApplicationModel.create({
142 141 migrationVersion: LAST_MIGRATION_VERSION
143 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 142 })
144 accountCreated.set('publicKey', publicKey)
145 accountCreated.set('privateKey', privateKey)
146 143
147 return accountCreated.save() 144 return createApplicationActor(application.id)
148} 145}
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index fb42e1d57..d896b3205 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -5,7 +5,7 @@ import { shareVideoByServer } from '../../lib/activitypub/share'
5import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' 5import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
6import { createLocalAccountWithoutKeys } from '../../lib/user' 6import { createLocalAccountWithoutKeys } from '../../lib/user'
7import { ApplicationModel } from '../../models/application/application' 7import { ApplicationModel } from '../../models/application/application'
8import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' 8import { JOB_CATEGORIES, SERVER_ACTOR_NAME } from '../constants'
9 9
10async function up (utils: { 10async function up (utils: {
11 transaction: Sequelize.Transaction, 11 transaction: Sequelize.Transaction,
@@ -66,7 +66,7 @@ async function up (utils: {
66 // Create application account 66 // Create application account
67 { 67 {
68 const applicationInstance = await ApplicationModel.findOne() 68 const applicationInstance = await ApplicationModel.findOne()
69 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined) 69 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined)
70 70
71 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 71 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
72 accountCreated.set('publicKey', publicKey) 72 accountCreated.set('publicKey', publicKey)
diff --git a/server/lib/activitypub/account.ts b/server/lib/activitypub/account.ts
deleted file mode 100644
index 45690b88d..000000000
--- a/server/lib/activitypub/account.ts
+++ /dev/null
@@ -1,127 +0,0 @@
1import * as Bluebird from 'bluebird'
2import { Transaction } from 'sequelize'
3import * as url from 'url'
4import { ActivityPubActor } from '../../../shared/models/activitypub'
5import { doRequest, logger, retryTransactionWrapper } from '../../helpers'
6import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub'
7import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers'
8import { AccountModel } from '../../models/account/account'
9import { ServerModel } from '../../models/server/server'
10
11async function getOrCreateAccountAndServer (accountUrl: string) {
12 let account = await AccountModel.loadByUrl(accountUrl)
13
14 // We don't have this account in our database, fetch it on remote
15 if (!account) {
16 account = await fetchRemoteAccount(accountUrl)
17 if (account === undefined) throw new Error('Cannot fetch remote account.')
18
19 const options = {
20 arguments: [ account ],
21 errorMessage: 'Cannot save account and server with many retries.'
22 }
23 account = await retryTransactionWrapper(saveAccountAndServerIfNotExist, options)
24 }
25
26 return account
27}
28
29function saveAccountAndServerIfNotExist (account: AccountModel, t?: Transaction): Bluebird<AccountModel> | Promise<AccountModel> {
30 if (t !== undefined) {
31 return save(t)
32 } else {
33 return sequelizeTypescript.transaction(t => {
34 return save(t)
35 })
36 }
37
38 async function save (t: Transaction) {
39 const accountHost = url.parse(account.url).host
40
41 const serverOptions = {
42 where: {
43 host: accountHost
44 },
45 defaults: {
46 host: accountHost
47 },
48 transaction: t
49 }
50 const [ server ] = await ServerModel.findOrCreate(serverOptions)
51
52 // Save our new account in database
53 account.set('serverId', server.id)
54 account = await account.save({ transaction: t })
55
56 return account
57 }
58}
59
60async function fetchRemoteAccount (accountUrl: string) {
61 const options = {
62 uri: accountUrl,
63 method: 'GET',
64 headers: {
65 'Accept': ACTIVITY_PUB.ACCEPT_HEADER
66 }
67 }
68
69 logger.info('Fetching remote account %s.', accountUrl)
70
71 let requestResult
72 try {
73 requestResult = await doRequest(options)
74 } catch (err) {
75 logger.warn('Cannot fetch remote account %s.', accountUrl, err)
76 return undefined
77 }
78
79 const accountJSON: ActivityPubActor = JSON.parse(requestResult.body)
80 if (isRemoteAccountValid(accountJSON) === false) {
81 logger.debug('Remote account JSON is not valid.', { accountJSON })
82 return undefined
83 }
84
85 const followersCount = await fetchAccountCount(accountJSON.followers)
86 const followingCount = await fetchAccountCount(accountJSON.following)
87
88 return new AccountModel({
89 uuid: accountJSON.uuid,
90 name: accountJSON.preferredUsername,
91 url: accountJSON.url,
92 publicKey: accountJSON.publicKey.publicKeyPem,
93 privateKey: null,
94 followersCount: followersCount,
95 followingCount: followingCount,
96 inboxUrl: accountJSON.inbox,
97 outboxUrl: accountJSON.outbox,
98 sharedInboxUrl: accountJSON.endpoints.sharedInbox,
99 followersUrl: accountJSON.followers,
100 followingUrl: accountJSON.following
101 })
102}
103
104export {
105 getOrCreateAccountAndServer,
106 fetchRemoteAccount,
107 saveAccountAndServerIfNotExist
108}
109
110// ---------------------------------------------------------------------------
111
112async function fetchAccountCount (url: string) {
113 const options = {
114 uri: url,
115 method: 'GET'
116 }
117
118 let requestResult
119 try {
120 requestResult = await doRequest(options)
121 } catch (err) {
122 logger.warn('Cannot fetch remote account count %s.', url, err)
123 return undefined
124 }
125
126 return requestResult.totalItems ? requestResult.totalItems : 0
127}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
new file mode 100644
index 000000000..c3de4bdce
--- /dev/null
+++ b/server/lib/activitypub/actor.ts
@@ -0,0 +1,229 @@
1import * as Bluebird from 'bluebird'
2import { Transaction } from 'sequelize'
3import * as url from 'url'
4import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
5import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
6import { createPrivateAndPublicKeys, doRequest, logger, retryTransactionWrapper } from '../../helpers'
7import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub'
8import { ACTIVITY_PUB, CONFIG, sequelizeTypescript } from '../../initializers'
9import { AccountModel } from '../../models/account/account'
10import { ActorModel } from '../../models/activitypub/actor'
11import { ServerModel } from '../../models/server/server'
12import { VideoChannelModel } from '../../models/video/video-channel'
13
14 // Set account keys, this could be long so process after the account creation and do not block the client
15function setAsyncActorKeys (actor: ActorModel) {
16 return createPrivateAndPublicKeys()
17 .then(({ publicKey, privateKey }) => {
18 actor.set('publicKey', publicKey)
19 actor.set('privateKey', privateKey)
20 return actor.save()
21 })
22 .catch(err => {
23 logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err)
24 return actor
25 })
26}
27
28async function getOrCreateActorAndServerAndModel (actorUrl: string, recurseIfNeeded = true) {
29 let actor = await ActorModel.loadByUrl(actorUrl)
30
31 // We don't have this actor in our database, fetch it on remote
32 if (!actor) {
33 const result = await fetchRemoteActor(actorUrl)
34 if (result === undefined) throw new Error('Cannot fetch remote actor.')
35
36 // Create the attributed to actor
37 // In PeerTube a video channel is owned by an account
38 let ownerActor: ActorModel = undefined
39 if (recurseIfNeeded === true && result.actor.type === 'Group') {
40 const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
41 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
42
43 try {
44 // Assert we don't recurse another time
45 ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, false)
46 } catch (err) {
47 logger.error('Cannot get or create account attributed to video channel ' + actor.url)
48 throw new Error(err)
49 }
50 }
51
52 const options = {
53 arguments: [ result, ownerActor ],
54 errorMessage: 'Cannot save actor and server with many retries.'
55 }
56 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
57 }
58
59 return actor
60}
61
62function saveActorAndServerAndModelIfNotExist (
63 result: FetchRemoteActorResult,
64 ownerActor?: ActorModel,
65 t?: Transaction
66): Bluebird<ActorModel> | Promise<ActorModel> {
67 let actor = result.actor
68
69 if (t !== undefined) return save(t)
70
71 return sequelizeTypescript.transaction(t => save(t))
72
73 async function save (t: Transaction) {
74 const actorHost = url.parse(actor.url).host
75
76 const serverOptions = {
77 where: {
78 host: actorHost
79 },
80 defaults: {
81 host: actorHost
82 },
83 transaction: t
84 }
85 const [ server ] = await ServerModel.findOrCreate(serverOptions)
86
87 // Save our new account in database
88 actor.set('serverId', server.id)
89
90 // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
91 // (which could be false in a retried query)
92 const actorCreated = await ActorModel.create(actor.toJSON(), { transaction: t })
93
94 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
95 const account = await saveAccount(actorCreated, result, t)
96 actorCreated.Account = account
97 actorCreated.Account.Actor = actorCreated
98 } else if (actorCreated.type === 'Group') { // Video channel
99 const videoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
100 actorCreated.VideoChannel = videoChannel
101 actorCreated.VideoChannel.Actor = actorCreated
102 }
103
104 return actorCreated
105 }
106}
107
108type FetchRemoteActorResult = {
109 actor: ActorModel
110 preferredUsername: string
111 summary: string
112 attributedTo: ActivityPubAttributedTo[]
113}
114async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResult> {
115 const options = {
116 uri: actorUrl,
117 method: 'GET',
118 headers: {
119 'Accept': ACTIVITY_PUB.ACCEPT_HEADER
120 }
121 }
122
123 logger.info('Fetching remote actor %s.', actorUrl)
124
125 let requestResult
126 try {
127 requestResult = await doRequest(options)
128 } catch (err) {
129 logger.warn('Cannot fetch remote actor %s.', actorUrl, err)
130 return undefined
131 }
132
133 const actorJSON: ActivityPubActor = JSON.parse(requestResult.body)
134 if (isRemoteActorValid(actorJSON) === false) {
135 logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
136 return undefined
137 }
138
139 const followersCount = await fetchActorTotalItems(actorJSON.followers)
140 const followingCount = await fetchActorTotalItems(actorJSON.following)
141
142 const actor = new ActorModel({
143 type: actorJSON.type,
144 uuid: actorJSON.uuid,
145 name: actorJSON.name,
146 url: actorJSON.url,
147 publicKey: actorJSON.publicKey.publicKeyPem,
148 privateKey: null,
149 followersCount: followersCount,
150 followingCount: followingCount,
151 inboxUrl: actorJSON.inbox,
152 outboxUrl: actorJSON.outbox,
153 sharedInboxUrl: actorJSON.endpoints.sharedInbox,
154 followersUrl: actorJSON.followers,
155 followingUrl: actorJSON.following
156 })
157
158 return {
159 actor,
160 preferredUsername: actorJSON.preferredUsername,
161 summary: actorJSON.summary,
162 attributedTo: actorJSON.attributedTo
163 }
164}
165
166function buildActorInstance (type: ActivityPubActorType, url: string, name: string, uuid?: string) {
167 return new ActorModel({
168 type,
169 url,
170 name,
171 uuid,
172 publicKey: null,
173 privateKey: null,
174 followersCount: 0,
175 followingCount: 0,
176 inboxUrl: url + '/inbox',
177 outboxUrl: url + '/outbox',
178 sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
179 followersUrl: url + '/followers',
180 followingUrl: url + '/following'
181 })
182}
183
184export {
185 getOrCreateActorAndServerAndModel,
186 saveActorAndServerAndModelIfNotExist,
187 fetchRemoteActor,
188 buildActorInstance,
189 setAsyncActorKeys
190}
191
192// ---------------------------------------------------------------------------
193
194async function fetchActorTotalItems (url: string) {
195 const options = {
196 uri: url,
197 method: 'GET'
198 }
199
200 let requestResult
201 try {
202 requestResult = await doRequest(options)
203 } catch (err) {
204 logger.warn('Cannot fetch remote actor count %s.', url, err)
205 return undefined
206 }
207
208 return requestResult.totalItems ? requestResult.totalItems : 0
209}
210
211function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
212 const account = new AccountModel({
213 name: result.preferredUsername,
214 actorId: actor.id
215 })
216
217 return account.save({ transaction: t })
218}
219
220async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
221 const videoChannel = new VideoChannelModel({
222 name: result.preferredUsername,
223 description: result.summary,
224 actorId: actor.id,
225 accountId: ownerActor.Account.id
226 })
227
228 return videoChannel.save({ transaction: t })
229}
diff --git a/server/lib/activitypub/fetch.ts b/server/lib/activitypub/fetch.ts
index aa4dea8e0..4fc97cc38 100644
--- a/server/lib/activitypub/fetch.ts
+++ b/server/lib/activitypub/fetch.ts
@@ -1,10 +1,10 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { AccountModel } from '../../models/account/account' 2import { ActorModel } from '../../models/activitypub/actor'
3import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' 3import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler'
4 4
5async function addFetchOutboxJob (account: AccountModel, t: Transaction) { 5async function addFetchOutboxJob (actor: ActorModel, t: Transaction) {
6 const jobPayload: ActivityPubHttpPayload = { 6 const jobPayload: ActivityPubHttpPayload = {
7 uris: [ account.outboxUrl ] 7 uris: [ actor.outboxUrl ]
8 } 8 }
9 9
10 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpFetcherHandler', jobPayload) 10 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpFetcherHandler', jobPayload)
diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts
index fcea662a6..94ed1edaa 100644
--- a/server/lib/activitypub/index.ts
+++ b/server/lib/activitypub/index.ts
@@ -1,8 +1,7 @@
1export * from './process' 1export * from './process'
2export * from './send' 2export * from './send'
3export * from './account' 3export * from './actor'
4export * from './fetch' 4export * from './fetch'
5export * from './share' 5export * from './share'
6export * from './video-channels'
7export * from './videos' 6export * from './videos'
8export * from './url' 7export * from './url'
diff --git a/server/lib/activitypub/process/index.ts b/server/lib/activitypub/process/index.ts
index e25c261cc..db4980a72 100644
--- a/server/lib/activitypub/process/index.ts
+++ b/server/lib/activitypub/process/index.ts
@@ -1,6 +1,5 @@
1export * from './process' 1export * from './process'
2export * from './process-accept' 2export * from './process-accept'
3export * from './process-add'
4export * from './process-announce' 3export * from './process-announce'
5export * from './process-create' 4export * from './process-create'
6export * from './process-delete' 5export * from './process-delete'
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index a775c858a..a9c6f913c 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -1,29 +1,13 @@
1import * as magnetUtil from 'magnet-uri' 1import * as magnetUtil from 'magnet-uri'
2import { VideoTorrentObject } from '../../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { VideoChannelObject } from '../../../../shared/models/activitypub/objects'
4import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
5import { doRequest } from '../../../helpers' 4import { doRequest } from '../../../helpers'
6import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' 5import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
7import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' 6import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers'
8import { AccountModel } from '../../../models/account/account'
9import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
10import { VideoChannelModel } from '../../../models/video/video-channel' 8import { VideoChannelModel } from '../../../models/video/video-channel'
11import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
12import { VideoShareModel } from '../../../models/video/video-share' 9import { VideoShareModel } from '../../../models/video/video-share'
13import { getOrCreateAccountAndServer } from '../account' 10import { getOrCreateActorAndServerAndModel } from '../actor'
14
15function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountModel) {
16 return {
17 name: videoChannelObject.name,
18 description: videoChannelObject.content,
19 uuid: videoChannelObject.uuid,
20 url: videoChannelObject.id,
21 createdAt: new Date(videoChannelObject.published),
22 updatedAt: new Date(videoChannelObject.updated),
23 remote: true,
24 accountId: account.id
25 }
26}
27 11
28async function videoActivityObjectToDBAttributes ( 12async function videoActivityObjectToDBAttributes (
29 videoChannel: VideoChannelModel, 13 videoChannel: VideoChannelModel,
@@ -120,13 +104,13 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
120 uri: share, 104 uri: share,
121 json: true 105 json: true
122 }) 106 })
123 const actor = json['actor'] 107 const actorUrl = json['actor']
124 if (!actor) continue 108 if (!actorUrl) continue
125 109
126 const account = await getOrCreateAccountAndServer(actor) 110 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
127 111
128 const entry = { 112 const entry = {
129 accountId: account.id, 113 actorId: actor.id,
130 videoId: instance.id 114 videoId: instance.id
131 } 115 }
132 116
@@ -137,36 +121,10 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
137 } 121 }
138} 122}
139 123
140async function addVideoChannelShares (instance: VideoChannelModel, shares: string[]) {
141 for (const share of shares) {
142 // Fetch url
143 const json = await doRequest({
144 uri: share,
145 json: true
146 })
147 const actor = json['actor']
148 if (!actor) continue
149
150 const account = await getOrCreateAccountAndServer(actor)
151
152 const entry = {
153 accountId: account.id,
154 videoChannelId: instance.id
155 }
156
157 await VideoChannelShareModel.findOrCreate({
158 where: entry,
159 defaults: entry
160 })
161 }
162}
163
164// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
165 125
166export { 126export {
167 videoFileActivityUrlToDBAttributes, 127 videoFileActivityUrlToDBAttributes,
168 videoActivityObjectToDBAttributes, 128 videoActivityObjectToDBAttributes,
169 videoChannelActivityObjectToDBAttributes,
170 addVideoChannelShares,
171 addVideoShares 129 addVideoShares
172} 130}
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 5b321f771..b9d906ec9 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,14 +1,14 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { AccountModel } from '../../../models/account/account' 2import { ActorModel } from '../../../models/activitypub/actor'
3import { AccountFollowModel } from '../../../models/account/account-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { addFetchOutboxJob } from '../fetch' 4import { addFetchOutboxJob } from '../fetch'
5 5
6async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) { 6async function processAcceptActivity (activity: ActivityAccept, inboxActor?: ActorModel) {
7 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.') 7 if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
8 8
9 const targetAccount = await AccountModel.loadByUrl(activity.actor) 9 const targetActor = await ActorModel.loadByUrl(activity.actor)
10 10
11 return processAccept(inboxAccount, targetAccount) 11 return processAccept(inboxActor, targetActor)
12} 12}
13 13
14// --------------------------------------------------------------------------- 14// ---------------------------------------------------------------------------
@@ -19,11 +19,11 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processAccept (account: AccountModel, targetAccount: AccountModel) { 22async function processAccept (actor: ActorModel, targetActor: ActorModel) {
23 const follow = await AccountFollowModel.loadByAccountAndTarget(account.id, targetAccount.id) 23 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
24 if (!follow) throw new Error('Cannot find associated follow.') 24 if (!follow) throw new Error('Cannot find associated follow.')
25 25
26 follow.set('state', 'accepted') 26 follow.set('state', 'accepted')
27 await follow.save() 27 await follow.save()
28 await addFetchOutboxJob(targetAccount, undefined) 28 await addFetchOutboxJob(targetActor, undefined)
29} 29}
diff --git a/server/lib/activitypub/process/process-add.ts b/server/lib/activitypub/process/process-add.ts
deleted file mode 100644
index 550593eab..000000000
--- a/server/lib/activitypub/process/process-add.ts
+++ /dev/null
@@ -1,137 +0,0 @@
1import * as Bluebird from 'bluebird'
2import { VideoTorrentObject } from '../../../../shared'
3import { ActivityAdd } from '../../../../shared/models/activitypub'
4import { VideoRateType } from '../../../../shared/models/videos'
5import { logger, retryTransactionWrapper } from '../../../helpers'
6import { sequelizeTypescript } from '../../../initializers'
7import { AccountModel } from '../../../models/account/account'
8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
9import { TagModel } from '../../../models/video/tag'
10import { VideoModel } from '../../../models/video/video'
11import { VideoChannelModel } from '../../../models/video/video-channel'
12import { VideoFileModel } from '../../../models/video/video-file'
13import { getOrCreateAccountAndServer } from '../account'
14import { getOrCreateVideoChannel } from '../video-channels'
15import { generateThumbnailFromUrl } from '../videos'
16import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
17
18async function processAddActivity (activity: ActivityAdd) {
19 const activityObject = activity.object
20 const activityType = activityObject.type
21 const account = await getOrCreateAccountAndServer(activity.actor)
22
23 if (activityType === 'Video') {
24 const videoChannelUrl = activity.target
25 const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
26
27 return processAddVideo(account, activity, videoChannel, activityObject)
28 }
29
30 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
31 return Promise.resolve(undefined)
32}
33
34// ---------------------------------------------------------------------------
35
36export {
37 processAddActivity
38}
39
40// ---------------------------------------------------------------------------
41
42async function processAddVideo (account: AccountModel,
43 activity: ActivityAdd,
44 videoChannel: VideoChannelModel,
45 videoToCreateData: VideoTorrentObject) {
46 const options = {
47 arguments: [ account, activity, videoChannel, videoToCreateData ],
48 errorMessage: 'Cannot insert the remote video with many retries.'
49 }
50
51 const video = await retryTransactionWrapper(addRemoteVideo, options)
52
53 // Process outside the transaction because we could fetch remote data
54 if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
55 await createRates(videoToCreateData.likes.orderedItems, video, 'like')
56 }
57
58 if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
59 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
60 }
61
62 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
63 await addVideoShares(video, videoToCreateData.shares.orderedItems)
64 }
65
66 return video
67}
68
69function addRemoteVideo (account: AccountModel,
70 activity: ActivityAdd,
71 videoChannel: VideoChannelModel,
72 videoToCreateData: VideoTorrentObject) {
73 logger.debug('Adding remote video %s.', videoToCreateData.id)
74
75 return sequelizeTypescript.transaction(async t => {
76 const sequelizeOptions = {
77 transaction: t
78 }
79
80 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
81
82 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
83 if (videoFromDatabase) return videoFromDatabase
84
85 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
86 const video = VideoModel.build(videoData)
87
88 // Don't block on request
89 generateThumbnailFromUrl(video, videoToCreateData.icon)
90 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
91
92 const videoCreated = await video.save(sequelizeOptions)
93
94 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
95 if (videoFileAttributes.length === 0) {
96 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
97 }
98
99 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
100 await Promise.all(tasks)
101
102 const tags = videoToCreateData.tag.map(t => t.name)
103 const tagInstances = await TagModel.findOrCreateTags(tags, t)
104 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
105
106 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
107
108 return videoCreated
109 })
110}
111
112async function createRates (accountUrls: string[], video: VideoModel, rate: VideoRateType) {
113 let rateCounts = 0
114 const tasks: Bluebird<any>[] = []
115
116 for (const accountUrl of accountUrls) {
117 const account = await getOrCreateAccountAndServer(accountUrl)
118 const p = AccountVideoRateModel
119 .create({
120 videoId: video.id,
121 accountId: account.id,
122 type: rate
123 })
124 .then(() => rateCounts += 1)
125
126 tasks.push(p)
127 }
128
129 await Promise.all(tasks)
130
131 logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
132
133 // This is "likes" and "dislikes"
134 await video.increment(rate + 's', { by: rateCounts })
135
136 return
137}
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index ff2c6d708..7dfee2f60 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -1,24 +1,19 @@
1import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub' 1import { ActivityAnnounce } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel'
7import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
8import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
9import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateActorAndServerAndModel } from '../actor'
10import { forwardActivity } from '../send/misc' 8import { forwardActivity } from '../send/misc'
11import { processAddActivity } from './process-add'
12import { processCreateActivity } from './process-create' 9import { processCreateActivity } from './process-create'
13 10
14async function processAnnounceActivity (activity: ActivityAnnounce) { 11async function processAnnounceActivity (activity: ActivityAnnounce) {
15 const announcedActivity = activity.object 12 const announcedActivity = activity.object
16 const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor) 13 const actorAnnouncer = await getOrCreateActorAndServerAndModel(activity.actor)
17 14
18 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') { 15 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'Video') {
19 return processVideoChannelShare(accountAnnouncer, activity) 16 return processVideoShare(actorAnnouncer, activity)
20 } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
21 return processVideoShare(accountAnnouncer, activity)
22 } 17 }
23 18
24 logger.warn( 19 logger.warn(
@@ -37,60 +32,24 @@ export {
37 32
38// --------------------------------------------------------------------------- 33// ---------------------------------------------------------------------------
39 34
40function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { 35function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
41 const options = { 36 const options = {
42 arguments: [ accountAnnouncer, activity ], 37 arguments: [ actorAnnouncer, activity ],
43 errorMessage: 'Cannot share the video channel with many retries.'
44 }
45
46 return retryTransactionWrapper(shareVideoChannel, options)
47}
48
49async function shareVideoChannel (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
50 const announcedActivity = activity.object as ActivityCreate
51
52 return sequelizeTypescript.transaction(async t => {
53 // Add share entry
54 const videoChannel: VideoChannelModel = await processCreateActivity(announcedActivity)
55 const share = {
56 accountId: accountAnnouncer.id,
57 videoChannelId: videoChannel.id
58 }
59
60 const [ , created ] = await VideoChannelShareModel.findOrCreate({
61 where: share,
62 defaults: share,
63 transaction: t
64 })
65
66 if (videoChannel.isOwned() && created === true) {
67 // Don't resend the activity to the sender
68 const exceptions = [ accountAnnouncer ]
69 await forwardActivity(activity, t, exceptions)
70 }
71
72 return undefined
73 })
74}
75
76function processVideoShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
77 const options = {
78 arguments: [ accountAnnouncer, activity ],
79 errorMessage: 'Cannot share the video with many retries.' 38 errorMessage: 'Cannot share the video with many retries.'
80 } 39 }
81 40
82 return retryTransactionWrapper(shareVideo, options) 41 return retryTransactionWrapper(shareVideo, options)
83} 42}
84 43
85function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { 44function shareVideo (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
86 const announcedActivity = activity.object as ActivityAdd 45 const announcedActivity = activity.object
87 46
88 return sequelizeTypescript.transaction(async t => { 47 return sequelizeTypescript.transaction(async t => {
89 // Add share entry 48 // Add share entry
90 const video: VideoModel = await processAddActivity(announcedActivity) 49 const video: VideoModel = await processCreateActivity(announcedActivity)
91 50
92 const share = { 51 const share = {
93 accountId: accountAnnouncer.id, 52 actorId: actorAnnouncer.id,
94 videoId: video.id 53 videoId: video.id
95 } 54 }
96 55
@@ -102,7 +61,7 @@ function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce)
102 61
103 if (video.isOwned() && created === true) { 62 if (video.isOwned() && created === true) {
104 // Don't resend the activity to the sender 63 // Don't resend the activity to the sender
105 const exceptions = [ accountAnnouncer ] 64 const exceptions = [ actorAnnouncer ]
106 await forwardActivity(activity, t, exceptions) 65 await forwardActivity(activity, t, exceptions)
107 } 66 }
108 67
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index c1eb2a8ab..1ddd817db 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,30 +1,33 @@
1import { ActivityCreate, VideoChannelObject } from '../../../../shared' 1import * as Bluebird from 'bluebird'
2import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' 3import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
4import { VideoRateType } from '../../../../shared/models/videos'
3import { logger, retryTransactionWrapper } from '../../../helpers' 5import { logger, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript } from '../../../initializers' 6import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account'
6import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 7import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
8import { ActorModel } from '../../../models/activitypub/actor'
9import { TagModel } from '../../../models/video/tag'
7import { VideoModel } from '../../../models/video/video' 10import { VideoModel } from '../../../models/video/video'
8import { VideoAbuseModel } from '../../../models/video/video-abuse' 11import { VideoAbuseModel } from '../../../models/video/video-abuse'
9import { VideoChannelModel } from '../../../models/video/video-channel' 12import { VideoFileModel } from '../../../models/video/video-file'
10import { getOrCreateAccountAndServer } from '../account' 13import { getOrCreateActorAndServerAndModel } from '../actor'
11import { forwardActivity } from '../send/misc' 14import { forwardActivity } from '../send/misc'
12import { getVideoChannelActivityPubUrl } from '../url' 15import { generateThumbnailFromUrl } from '../videos'
13import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc' 16import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
14 17
15async function processCreateActivity (activity: ActivityCreate) { 18async function processCreateActivity (activity: ActivityCreate) {
16 const activityObject = activity.object 19 const activityObject = activity.object
17 const activityType = activityObject.type 20 const activityType = activityObject.type
18 const account = await getOrCreateAccountAndServer(activity.actor) 21 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
19 22
20 if (activityType === 'View') { 23 if (activityType === 'View') {
21 return processCreateView(account, activity) 24 return processCreateView(actor, activity)
22 } else if (activityType === 'Dislike') { 25 } else if (activityType === 'Dislike') {
23 return processCreateDislike(account, activity) 26 return processCreateDislike(actor, activity)
24 } else if (activityType === 'VideoChannel') { 27 } else if (activityType === 'Video') {
25 return processCreateVideoChannel(account, activityObject as VideoChannelObject) 28 return processCreateVideo(actor, activity)
26 } else if (activityType === 'Flag') { 29 } else if (activityType === 'Flag') {
27 return processCreateVideoAbuse(account, activityObject as VideoAbuseObject) 30 return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
28 } 31 }
29 32
30 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) 33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -39,17 +42,123 @@ export {
39 42
40// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
41 44
42async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) { 45async function processCreateVideo (
46 actor: ActorModel,
47 activity: ActivityCreate
48) {
49 const videoToCreateData = activity.object as VideoTorrentObject
50
51 const channel = videoToCreateData.attributedTo.find(a => a.type === 'Group')
52 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoToCreateData.url)
53
54 const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
55
56 const options = {
57 arguments: [ actor, activity, videoToCreateData, channelActor ],
58 errorMessage: 'Cannot insert the remote video with many retries.'
59 }
60
61 const video = await retryTransactionWrapper(createRemoteVideo, options)
62
63 // Process outside the transaction because we could fetch remote data
64 if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
65 await createRates(videoToCreateData.likes.orderedItems, video, 'like')
66 }
67
68 if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
69 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
70 }
71
72 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
73 await addVideoShares(video, videoToCreateData.shares.orderedItems)
74 }
75
76 return video
77}
78
79function createRemoteVideo (
80 account: ActorModel,
81 activity: ActivityCreate,
82 videoToCreateData: VideoTorrentObject,
83 channelActor: ActorModel
84) {
85 logger.debug('Adding remote video %s.', videoToCreateData.id)
86
87 return sequelizeTypescript.transaction(async t => {
88 const sequelizeOptions = {
89 transaction: t
90 }
91 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
92 if (videoFromDatabase) return videoFromDatabase
93
94 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoToCreateData, activity.to, activity.cc)
95 const video = VideoModel.build(videoData)
96
97 // Don't block on request
98 generateThumbnailFromUrl(video, videoToCreateData.icon)
99 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
100
101 const videoCreated = await video.save(sequelizeOptions)
102
103 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
104 if (videoFileAttributes.length === 0) {
105 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
106 }
107
108 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
109 await Promise.all(tasks)
110
111 const tags = videoToCreateData.tag.map(t => t.name)
112 const tagInstances = await TagModel.findOrCreateTags(tags, t)
113 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
114
115 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
116
117 return videoCreated
118 })
119}
120
121async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
122 let rateCounts = 0
123 const tasks: Bluebird<any>[] = []
124
125 for (const actorUrl of actorUrls) {
126 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
127 const p = AccountVideoRateModel
128 .create({
129 videoId: video.id,
130 accountId: actor.Account.id,
131 type: rate
132 })
133 .then(() => rateCounts += 1)
134
135 tasks.push(p)
136 }
137
138 await Promise.all(tasks)
139
140 logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
141
142 // This is "likes" and "dislikes"
143 await video.increment(rate + 's', { by: rateCounts })
144
145 return
146}
147
148async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
43 const options = { 149 const options = {
44 arguments: [ byAccount, activity ], 150 arguments: [ byActor, activity ],
45 errorMessage: 'Cannot dislike the video with many retries.' 151 errorMessage: 'Cannot dislike the video with many retries.'
46 } 152 }
47 153
48 return retryTransactionWrapper(createVideoDislike, options) 154 return retryTransactionWrapper(createVideoDislike, options)
49} 155}
50 156
51function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) { 157function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
52 const dislike = activity.object as DislikeObject 158 const dislike = activity.object as DislikeObject
159 const byAccount = byActor.Account
160
161 if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
53 162
54 return sequelizeTypescript.transaction(async t => { 163 return sequelizeTypescript.transaction(async t => {
55 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) 164 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
@@ -69,20 +178,20 @@ function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate)
69 178
70 if (video.isOwned() && created === true) { 179 if (video.isOwned() && created === true) {
71 // Don't resend the activity to the sender 180 // Don't resend the activity to the sender
72 const exceptions = [ byAccount ] 181 const exceptions = [ byActor ]
73 await forwardActivity(activity, t, exceptions) 182 await forwardActivity(activity, t, exceptions)
74 } 183 }
75 }) 184 })
76} 185}
77 186
78async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) { 187async function processCreateView (byAccount: ActorModel, activity: ActivityCreate) {
79 const view = activity.object as ViewObject 188 const view = activity.object as ViewObject
80 189
81 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object) 190 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
82 191
83 if (!video) throw new Error('Unknown video ' + view.object) 192 if (!video) throw new Error('Unknown video ' + view.object)
84 193
85 const account = await AccountModel.loadByUrl(view.actor) 194 const account = await ActorModel.loadByUrl(view.actor)
86 if (!account) throw new Error('Unknown account ' + view.actor) 195 if (!account) throw new Error('Unknown account ' + view.actor)
87 196
88 await video.increment('views') 197 await video.increment('views')
@@ -94,51 +203,21 @@ async function processCreateView (byAccount: AccountModel, activity: ActivityCre
94 } 203 }
95} 204}
96 205
97async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) { 206function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
98 const options = {
99 arguments: [ account, videoChannelToCreateData ],
100 errorMessage: 'Cannot insert the remote video channel with many retries.'
101 }
102
103 const videoChannel = await retryTransactionWrapper(addRemoteVideoChannel, options)
104
105 if (videoChannelToCreateData.shares && Array.isArray(videoChannelToCreateData.shares.orderedItems)) {
106 await addVideoChannelShares(videoChannel, videoChannelToCreateData.shares.orderedItems)
107 }
108
109 return videoChannel
110}
111
112function addRemoteVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
113 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
114
115 return sequelizeTypescript.transaction(async t => {
116 let videoChannel = await VideoChannelModel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
117 if (videoChannel) return videoChannel
118
119 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
120 videoChannel = new VideoChannelModel(videoChannelData)
121 videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
122
123 videoChannel = await videoChannel.save({ transaction: t })
124 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
125
126 return videoChannel
127 })
128}
129
130function processCreateVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
131 const options = { 207 const options = {
132 arguments: [ account, videoAbuseToCreateData ], 208 arguments: [ actor, videoAbuseToCreateData ],
133 errorMessage: 'Cannot insert the remote video abuse with many retries.' 209 errorMessage: 'Cannot insert the remote video abuse with many retries.'
134 } 210 }
135 211
136 return retryTransactionWrapper(addRemoteVideoAbuse, options) 212 return retryTransactionWrapper(addRemoteVideoAbuse, options)
137} 213}
138 214
139function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) { 215function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
140 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) 216 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
141 217
218 const account = actor.Account
219 if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)
220
142 return sequelizeTypescript.transaction(async t => { 221 return sequelizeTypescript.transaction(async t => {
143 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) 222 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
144 if (!video) { 223 if (!video) {
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 8f280d37f..65a4e5bcc 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -2,28 +2,30 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { AccountModel } from '../../../models/account/account'
5import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel' 7import { VideoChannelModel } from '../../../models/video/video-channel'
7import { getOrCreateAccountAndServer } from '../account' 8import { getOrCreateActorAndServerAndModel } from '../actor'
8 9
9async function processDeleteActivity (activity: ActivityDelete) { 10async function processDeleteActivity (activity: ActivityDelete) {
10 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
11 12
12 if (account.url === activity.id) { 13 if (actor.url === activity.id) {
13 return processDeleteAccount(account) 14 if (actor.type === 'Person') {
14 } 15 if (!actor.Account) throw new Error('Actor ' + actor.url + ' is a person but we cannot find it in database.')
15 16
16 { 17 return processDeleteAccount(actor.Account)
17 let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id) 18 } else if (actor.type === 'Group') {
18 if (videoObject !== undefined) { 19 if (!actor.VideoChannel) throw new Error('Actor ' + actor.url + ' is a group but we cannot find it in database.')
19 return processDeleteVideo(account, videoObject) 20
21 return processDeleteVideoChannel(actor.VideoChannel)
20 } 22 }
21 } 23 }
22 24
23 { 25 {
24 let videoChannelObject = await VideoChannelModel.loadByUrl(activity.id) 26 let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
25 if (videoChannelObject !== undefined) { 27 if (videoObject !== undefined) {
26 return processDeleteVideoChannel(account, videoChannelObject) 28 return processDeleteVideo(actor, videoObject)
27 } 29 }
28 } 30 }
29 31
@@ -38,21 +40,21 @@ export {
38 40
39// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
40 42
41async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) { 43async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
42 const options = { 44 const options = {
43 arguments: [ account, videoToDelete ], 45 arguments: [ actor, videoToDelete ],
44 errorMessage: 'Cannot remove the remote video with many retries.' 46 errorMessage: 'Cannot remove the remote video with many retries.'
45 } 47 }
46 48
47 await retryTransactionWrapper(deleteRemoteVideo, options) 49 await retryTransactionWrapper(deleteRemoteVideo, options)
48} 50}
49 51
50async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) { 52async function deleteRemoteVideo (actor: ActorModel, videoToDelete: VideoModel) {
51 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 53 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
52 54
53 await sequelizeTypescript.transaction(async t => { 55 await sequelizeTypescript.transaction(async t => {
54 if (videoToDelete.VideoChannel.Account.id !== account.id) { 56 if (videoToDelete.VideoChannel.Account.Actor.id !== actor.id) {
55 throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url) 57 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoToDelete.VideoChannel.Actor.url)
56 } 58 }
57 59
58 await videoToDelete.destroy({ transaction: t }) 60 await videoToDelete.destroy({ transaction: t })
@@ -61,44 +63,40 @@ async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoMod
61 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) 63 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
62} 64}
63 65
64async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { 66async function processDeleteAccount (accountToRemove: AccountModel) {
65 const options = { 67 const options = {
66 arguments: [ account, videoChannelToRemove ], 68 arguments: [ accountToRemove ],
67 errorMessage: 'Cannot remove the remote video channel with many retries.' 69 errorMessage: 'Cannot remove the remote account with many retries.'
68 } 70 }
69 71
70 await retryTransactionWrapper(deleteRemoteVideoChannel, options) 72 await retryTransactionWrapper(deleteRemoteAccount, options)
71} 73}
72 74
73async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { 75async function deleteRemoteAccount (accountToRemove: AccountModel) {
74 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid) 76 logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid)
75 77
76 await sequelizeTypescript.transaction(async t => { 78 await sequelizeTypescript.transaction(async t => {
77 if (videoChannelToRemove.Account.id !== account.id) { 79 await accountToRemove.destroy({ transaction: t })
78 throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
79 }
80
81 await videoChannelToRemove.destroy({ transaction: t })
82 }) 80 })
83 81
84 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid) 82 logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid)
85} 83}
86 84
87async function processDeleteAccount (accountToRemove: AccountModel) { 85async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
88 const options = { 86 const options = {
89 arguments: [ accountToRemove ], 87 arguments: [ videoChannelToRemove ],
90 errorMessage: 'Cannot remove the remote account with many retries.' 88 errorMessage: 'Cannot remove the remote video channel with many retries.'
91 } 89 }
92 90
93 await retryTransactionWrapper(deleteRemoteAccount, options) 91 await retryTransactionWrapper(deleteRemoteVideoChannel, options)
94} 92}
95 93
96async function deleteRemoteAccount (accountToRemove: AccountModel) { 94async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel) {
97 logger.debug('Removing remote account "%s".', accountToRemove.uuid) 95 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid)
98 96
99 await sequelizeTypescript.transaction(async t => { 97 await sequelizeTypescript.transaction(async t => {
100 await accountToRemove.destroy({ transaction: t }) 98 await videoChannelToRemove.destroy({ transaction: t })
101 }) 99 })
102 100
103 logger.info('Remote account with uuid %s removed.', accountToRemove.uuid) 101 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
104} 102}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index ccaee43a6..ec7a331f3 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -1,16 +1,16 @@
1import { ActivityFollow } from '../../../../shared/models/activitypub' 1import { ActivityFollow } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { AccountFollowModel } from '../../../models/account/account-follow' 5import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { getOrCreateAccountAndServer } from '../account' 6import { getOrCreateActorAndServerAndModel } from '../actor'
7import { sendAccept } from '../send' 7import { sendAccept } from '../send'
8 8
9async function processFollowActivity (activity: ActivityFollow) { 9async function processFollowActivity (activity: ActivityFollow) {
10 const activityObject = activity.object 10 const activityObject = activity.object
11 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
12 12
13 return processFollow(account, activityObject) 13 return processFollow(actor, activityObject)
14} 14}
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
@@ -21,46 +21,46 @@ export {
21 21
22// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
23 23
24function processFollow (account: AccountModel, targetAccountURL: string) { 24function processFollow (actor: ActorModel, targetActorURL: string) {
25 const options = { 25 const options = {
26 arguments: [ account, targetAccountURL ], 26 arguments: [ actor, targetActorURL ],
27 errorMessage: 'Cannot follow with many retries.' 27 errorMessage: 'Cannot follow with many retries.'
28 } 28 }
29 29
30 return retryTransactionWrapper(follow, options) 30 return retryTransactionWrapper(follow, options)
31} 31}
32 32
33async function follow (account: AccountModel, targetAccountURL: string) { 33async function follow (actor: ActorModel, targetActorURL: string) {
34 await sequelizeTypescript.transaction(async t => { 34 await sequelizeTypescript.transaction(async t => {
35 const targetAccount = await AccountModel.loadByUrl(targetAccountURL, t) 35 const targetActor = await ActorModel.loadByUrl(targetActorURL, t)
36 36
37 if (!targetAccount) throw new Error('Unknown account') 37 if (!targetActor) throw new Error('Unknown actor')
38 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') 38 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
39 39
40 const [ accountFollow ] = await AccountFollowModel.findOrCreate({ 40 const [ actorFollow ] = await ActorFollowModel.findOrCreate({
41 where: { 41 where: {
42 accountId: account.id, 42 actorId: actor.id,
43 targetAccountId: targetAccount.id 43 targetActorId: targetActor.id
44 }, 44 },
45 defaults: { 45 defaults: {
46 accountId: account.id, 46 actorId: actor.id,
47 targetAccountId: targetAccount.id, 47 targetActorId: targetActor.id,
48 state: 'accepted' 48 state: 'accepted'
49 }, 49 },
50 transaction: t 50 transaction: t
51 }) 51 })
52 52
53 if (accountFollow.state !== 'accepted') { 53 if (actorFollow.state !== 'accepted') {
54 accountFollow.state = 'accepted' 54 actorFollow.state = 'accepted'
55 await accountFollow.save({ transaction: t }) 55 await actorFollow.save({ transaction: t })
56 } 56 }
57 57
58 accountFollow.AccountFollower = account 58 actorFollow.ActorFollower = actor
59 accountFollow.AccountFollowing = targetAccount 59 actorFollow.ActorFollowing = targetActor
60 60
61 // Target sends to account he accepted the follow request 61 // Target sends to actor he accepted the follow request
62 return sendAccept(accountFollow, t) 62 return sendAccept(actorFollow, t)
63 }) 63 })
64 64
65 logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL) 65 logger.info('Actor uuid %s is followed by actor %s.', actor.url, targetActorURL)
66} 66}
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index a6e391f1e..a7fcec21c 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -1,16 +1,16 @@
1import { ActivityLike } from '../../../../shared/models/activitypub' 1import { ActivityLike } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers' 2import { retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 4import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
5import { ActorModel } from '../../../models/activitypub/actor'
6import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
7import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateActorAndServerAndModel } from '../actor'
8import { forwardActivity } from '../send/misc' 8import { forwardActivity } from '../send/misc'
9 9
10async function processLikeActivity (activity: ActivityLike) { 10async function processLikeActivity (activity: ActivityLike) {
11 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
12 12
13 return processLikeVideo(account, activity) 13 return processLikeVideo(actor, activity)
14} 14}
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
@@ -21,18 +21,21 @@ export {
21 21
22// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
23 23
24async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) { 24async function processLikeVideo (actor: ActorModel, activity: ActivityLike) {
25 const options = { 25 const options = {
26 arguments: [ byAccount, activity ], 26 arguments: [ actor, activity ],
27 errorMessage: 'Cannot like the video with many retries.' 27 errorMessage: 'Cannot like the video with many retries.'
28 } 28 }
29 29
30 return retryTransactionWrapper(createVideoLike, options) 30 return retryTransactionWrapper(createVideoLike, options)
31} 31}
32 32
33function createVideoLike (byAccount: AccountModel, activity: ActivityLike) { 33function createVideoLike (byActor: ActorModel, activity: ActivityLike) {
34 const videoUrl = activity.object 34 const videoUrl = activity.object
35 35
36 const byAccount = byActor.Account
37 if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
38
36 return sequelizeTypescript.transaction(async t => { 39 return sequelizeTypescript.transaction(async t => {
37 const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) 40 const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl)
38 41
@@ -52,7 +55,7 @@ function createVideoLike (byAccount: AccountModel, activity: ActivityLike) {
52 55
53 if (video.isOwned() && created === true) { 56 if (video.isOwned() && created === true) {
54 // Don't resend the activity to the sender 57 // Don't resend the activity to the sender
55 const exceptions = [ byAccount ] 58 const exceptions = [ byActor ]
56 await forwardActivity(activity, t, exceptions) 59 await forwardActivity(activity, t, exceptions)
57 } 60 }
58 }) 61 })
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index efa63122b..4a0181137 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -3,8 +3,9 @@ import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { logger, retryTransactionWrapper } from '../../../helpers' 3import { logger, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account' 5import { AccountModel } from '../../../models/account/account'
6import { AccountFollowModel } from '../../../models/account/account-follow'
7import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 6import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
7import { ActorModel } from '../../../models/activitypub/actor'
8import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
8import { VideoModel } from '../../../models/video/video' 9import { VideoModel } from '../../../models/video/video'
9import { forwardActivity } from '../send/misc' 10import { forwardActivity } from '../send/misc'
10 11
@@ -32,21 +33,21 @@ export {
32 33
33// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
34 35
35function processUndoLike (actor: string, activity: ActivityUndo) { 36function processUndoLike (actorUrl: string, activity: ActivityUndo) {
36 const options = { 37 const options = {
37 arguments: [ actor, activity ], 38 arguments: [ actorUrl, activity ],
38 errorMessage: 'Cannot undo like with many retries.' 39 errorMessage: 'Cannot undo like with many retries.'
39 } 40 }
40 41
41 return retryTransactionWrapper(undoLike, options) 42 return retryTransactionWrapper(undoLike, options)
42} 43}
43 44
44function undoLike (actor: string, activity: ActivityUndo) { 45function undoLike (actorUrl: string, activity: ActivityUndo) {
45 const likeActivity = activity.object as ActivityLike 46 const likeActivity = activity.object as ActivityLike
46 47
47 return sequelizeTypescript.transaction(async t => { 48 return sequelizeTypescript.transaction(async t => {
48 const byAccount = await AccountModel.loadByUrl(actor, t) 49 const byAccount = await AccountModel.loadByUrl(actorUrl, t)
49 if (!byAccount) throw new Error('Unknown account ' + actor) 50 if (!byAccount) throw new Error('Unknown account ' + actorUrl)
50 51
51 const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t) 52 const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t)
52 if (!video) throw new Error('Unknown video ' + likeActivity.actor) 53 if (!video) throw new Error('Unknown video ' + likeActivity.actor)
@@ -59,27 +60,27 @@ function undoLike (actor: string, activity: ActivityUndo) {
59 60
60 if (video.isOwned()) { 61 if (video.isOwned()) {
61 // Don't resend the activity to the sender 62 // Don't resend the activity to the sender
62 const exceptions = [ byAccount ] 63 const exceptions = [ byAccount.Actor ]
63 await forwardActivity(activity, t, exceptions) 64 await forwardActivity(activity, t, exceptions)
64 } 65 }
65 }) 66 })
66} 67}
67 68
68function processUndoDislike (actor: string, activity: ActivityUndo) { 69function processUndoDislike (actorUrl: string, activity: ActivityUndo) {
69 const options = { 70 const options = {
70 arguments: [ actor, activity ], 71 arguments: [ actorUrl, activity ],
71 errorMessage: 'Cannot undo dislike with many retries.' 72 errorMessage: 'Cannot undo dislike with many retries.'
72 } 73 }
73 74
74 return retryTransactionWrapper(undoDislike, options) 75 return retryTransactionWrapper(undoDislike, options)
75} 76}
76 77
77function undoDislike (actor: string, activity: ActivityUndo) { 78function undoDislike (actorUrl: string, activity: ActivityUndo) {
78 const dislike = activity.object.object as DislikeObject 79 const dislike = activity.object.object as DislikeObject
79 80
80 return sequelizeTypescript.transaction(async t => { 81 return sequelizeTypescript.transaction(async t => {
81 const byAccount = await AccountModel.loadByUrl(actor, t) 82 const byAccount = await AccountModel.loadByUrl(actorUrl, t)
82 if (!byAccount) throw new Error('Unknown account ' + actor) 83 if (!byAccount) throw new Error('Unknown account ' + actorUrl)
83 84
84 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) 85 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
85 if (!video) throw new Error('Unknown video ' + dislike.actor) 86 if (!video) throw new Error('Unknown video ' + dislike.actor)
@@ -92,30 +93,30 @@ function undoDislike (actor: string, activity: ActivityUndo) {
92 93
93 if (video.isOwned()) { 94 if (video.isOwned()) {
94 // Don't resend the activity to the sender 95 // Don't resend the activity to the sender
95 const exceptions = [ byAccount ] 96 const exceptions = [ byAccount.Actor ]
96 await forwardActivity(activity, t, exceptions) 97 await forwardActivity(activity, t, exceptions)
97 } 98 }
98 }) 99 })
99} 100}
100 101
101function processUndoFollow (actor: string, followActivity: ActivityFollow) { 102function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
102 const options = { 103 const options = {
103 arguments: [ actor, followActivity ], 104 arguments: [ actorUrl, followActivity ],
104 errorMessage: 'Cannot undo follow with many retries.' 105 errorMessage: 'Cannot undo follow with many retries.'
105 } 106 }
106 107
107 return retryTransactionWrapper(undoFollow, options) 108 return retryTransactionWrapper(undoFollow, options)
108} 109}
109 110
110function undoFollow (actor: string, followActivity: ActivityFollow) { 111function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
111 return sequelizeTypescript.transaction(async t => { 112 return sequelizeTypescript.transaction(async t => {
112 const follower = await AccountModel.loadByUrl(actor, t) 113 const follower = await ActorModel.loadByUrl(actorUrl, t)
113 const following = await AccountModel.loadByUrl(followActivity.object, t) 114 const following = await ActorModel.loadByUrl(followActivity.object, t)
114 const accountFollow = await AccountFollowModel.loadByAccountAndTarget(follower.id, following.id, t) 115 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
115 116
116 if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`) 117 if (!actorFollow) throw new Error(`'Unknown actor follow ${follower.id} -> ${following.id}.`)
117 118
118 await accountFollow.destroy({ transaction: t }) 119 await actorFollow.destroy({ transaction: t })
119 120
120 return undefined 121 return undefined
121 }) 122 })
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 771021f0c..35912ee87 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,23 +1,19 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
3import { ActivityUpdate } from '../../../../shared/models/activitypub' 2import { ActivityUpdate } from '../../../../shared/models/activitypub'
4import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' 3import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
5import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
6import { AccountModel } from '../../../models/account/account' 5import { ActorModel } from '../../../models/activitypub/actor'
7import { TagModel } from '../../../models/video/tag' 6import { TagModel } from '../../../models/video/tag'
8import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
9import { VideoChannelModel } from '../../../models/video/video-channel'
10import { VideoFileModel } from '../../../models/video/video-file' 8import { VideoFileModel } from '../../../models/video/video-file'
11import { getOrCreateAccountAndServer } from '../account' 9import { getOrCreateActorAndServerAndModel } from '../actor'
12import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 10import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
13 11
14async function processUpdateActivity (activity: ActivityUpdate) { 12async function processUpdateActivity (activity: ActivityUpdate) {
15 const account = await getOrCreateAccountAndServer(activity.actor) 13 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
16 14
17 if (activity.object.type === 'Video') { 15 if (activity.object.type === 'Video') {
18 return processUpdateVideo(account, activity.object) 16 return processUpdateVideo(actor, activity)
19 } else if (activity.object.type === 'VideoChannel') {
20 return processUpdateVideoChannel(account, activity.object)
21 } 17 }
22 18
23 return 19 return
@@ -31,16 +27,18 @@ export {
31 27
32// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
33 29
34function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) { 30function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
35 const options = { 31 const options = {
36 arguments: [ account, video ], 32 arguments: [ actor, activity ],
37 errorMessage: 'Cannot update the remote video with many retries' 33 errorMessage: 'Cannot update the remote video with many retries'
38 } 34 }
39 35
40 return retryTransactionWrapper(updateRemoteVideo, options) 36 return retryTransactionWrapper(updateRemoteVideo, options)
41} 37}
42 38
43async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) { 39async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
40 const videoAttributesToUpdate = activity.object
41
44 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 42 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
45 let videoInstance: VideoModel 43 let videoInstance: VideoModel
46 let videoFieldsSave: object 44 let videoFieldsSave: object
@@ -54,23 +52,23 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
54 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) 52 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
55 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.') 53 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
56 54
57 if (videoInstance.VideoChannel.Account.id !== account.id) { 55 const videoChannel = videoInstance.VideoChannel
58 throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url) 56 if (videoChannel.Account.Actor.id !== actor.id) {
57 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
59 } 58 }
60 59
61 const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate) 60 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
62 videoInstance.set('name', videoData.name) 61 videoInstance.set('name', videoData.name)
63 videoInstance.set('category', videoData.category) 62 videoInstance.set('category', videoData.category)
64 videoInstance.set('licence', videoData.licence) 63 videoInstance.set('licence', videoData.licence)
65 videoInstance.set('language', videoData.language) 64 videoInstance.set('language', videoData.language)
66 videoInstance.set('nsfw', videoData.nsfw) 65 videoInstance.set('nsfw', videoData.nsfw)
66 videoInstance.set('privacy', videoData.privacy)
67 videoInstance.set('description', videoData.description) 67 videoInstance.set('description', videoData.description)
68 videoInstance.set('duration', videoData.duration) 68 videoInstance.set('duration', videoData.duration)
69 videoInstance.set('createdAt', videoData.createdAt) 69 videoInstance.set('createdAt', videoData.createdAt)
70 videoInstance.set('updatedAt', videoData.updatedAt) 70 videoInstance.set('updatedAt', videoData.updatedAt)
71 videoInstance.set('views', videoData.views) 71 videoInstance.set('views', videoData.views)
72 // videoInstance.set('likes', videoData.likes)
73 // videoInstance.set('dislikes', videoData.dislikes)
74 72
75 await videoInstance.save(sequelizeOptions) 73 await videoInstance.save(sequelizeOptions)
76 74
@@ -101,36 +99,3 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
101 throw err 99 throw err
102 } 100 }
103} 101}
104
105async function processUpdateVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
106 const options = {
107 arguments: [ account, videoChannel ],
108 errorMessage: 'Cannot update the remote video channel with many retries.'
109 }
110
111 await retryTransactionWrapper(updateRemoteVideoChannel, options)
112}
113
114async function updateRemoteVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
115 logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
116
117 await sequelizeTypescript.transaction(async t => {
118 const sequelizeOptions = { transaction: t }
119
120 const videoChannelInstance = await VideoChannelModel.loadByUrl(videoChannel.id)
121 if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
122
123 if (videoChannelInstance.Account.id !== account.id) {
124 throw new Error('Account ' + account.id + ' does not own video channel ' + videoChannelInstance.url)
125 }
126
127 videoChannelInstance.set('name', videoChannel.name)
128 videoChannelInstance.set('description', videoChannel.content)
129 videoChannelInstance.set('createdAt', videoChannel.published)
130 videoChannelInstance.set('updatedAt', videoChannel.updated)
131
132 await videoChannelInstance.save(sequelizeOptions)
133 })
134
135 logger.info('Remote video channel with uuid %s updated', videoChannel.uuid)
136}
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index bfbf8053c..dfb60c1bf 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,8 +1,7 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { logger } from '../../../helpers' 2import { logger } from '../../../helpers'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { processAcceptActivity } from './process-accept' 4import { processAcceptActivity } from './process-accept'
5import { processAddActivity } from './process-add'
6import { processAnnounceActivity } from './process-announce' 5import { processAnnounceActivity } from './process-announce'
7import { processCreateActivity } from './process-create' 6import { processCreateActivity } from './process-create'
8import { processDeleteActivity } from './process-delete' 7import { processDeleteActivity } from './process-delete'
@@ -11,9 +10,8 @@ import { processLikeActivity } from './process-like'
11import { processUndoActivity } from './process-undo' 10import { processUndoActivity } from './process-undo'
12import { processUpdateActivity } from './process-update' 11import { processUpdateActivity } from './process-update'
13 12
14const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = { 13const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxActor?: ActorModel) => Promise<any> } = {
15 Create: processCreateActivity, 14 Create: processCreateActivity,
16 Add: processAddActivity,
17 Update: processUpdateActivity, 15 Update: processUpdateActivity,
18 Delete: processDeleteActivity, 16 Delete: processDeleteActivity,
19 Follow: processFollowActivity, 17 Follow: processFollowActivity,
@@ -23,11 +21,11 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
23 Like: processLikeActivity 21 Like: processLikeActivity
24} 22}
25 23
26async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) { 24async function processActivities (activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel) {
27 for (const activity of activities) { 25 for (const activity of activities) {
28 // When we fetch remote data, we don't have signature 26 // When we fetch remote data, we don't have signature
29 if (signatureAccount && activity.actor !== signatureAccount.url) { 27 if (signatureActor && activity.actor !== signatureActor.url) {
30 logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureAccount.url) 28 logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureActor.url)
31 continue 29 continue
32 } 30 }
33 31
@@ -38,7 +36,7 @@ async function processActivities (activities: Activity[], signatureAccount?: Acc
38 } 36 }
39 37
40 try { 38 try {
41 await activityProcessor(activity, inboxAccount) 39 await activityProcessor(activity, inboxActor)
42 } catch (err) { 40 } catch (err) {
43 logger.warn('Cannot process activity %s.', activity.type, err) 41 logger.warn('Cannot process activity %s.', activity.type, err)
44 } 42 }
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts
index ee8f3ad7e..79ba6c7fe 100644
--- a/server/lib/activitypub/send/index.ts
+++ b/server/lib/activitypub/send/index.ts
@@ -1,5 +1,4 @@
1export * from './send-accept' 1export * from './send-accept'
2export * from './send-add'
3export * from './send-announce' 2export * from './send-announce'
4export * from './send-create' 3export * from './send-create'
5export * from './send-delete' 4export * from './send-delete'
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index ffc221477..14101e630 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -2,18 +2,16 @@ import { Transaction } from 'sequelize'
2import { Activity } from '../../../../shared/models/activitypub' 2import { Activity } from '../../../../shared/models/activitypub'
3import { logger } from '../../../helpers' 3import { logger } from '../../../helpers'
4import { ACTIVITY_PUB } from '../../../initializers' 4import { ACTIVITY_PUB } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account' 5import { ActorModel } from '../../../models/activitypub/actor'
6import { AccountFollowModel } from '../../../models/account/account-follow' 6import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
7import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
8import { VideoChannelModel } from '../../../models/video/video-channel'
9import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
10import { VideoShareModel } from '../../../models/video/video-share' 8import { VideoShareModel } from '../../../models/video/video-share'
11import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' 9import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler'
12 10
13async function forwardActivity ( 11async function forwardActivity (
14 activity: Activity, 12 activity: Activity,
15 t: Transaction, 13 t: Transaction,
16 followersException: AccountModel[] = [] 14 followersException: ActorModel[] = []
17) { 15) {
18 const to = activity.to || [] 16 const to = activity.to || []
19 const cc = activity.cc || [] 17 const cc = activity.cc || []
@@ -25,11 +23,11 @@ async function forwardActivity (
25 } 23 }
26 } 24 }
27 25
28 const toAccountFollowers = await AccountModel.listByFollowersUrls(followersUrls, t) 26 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
29 const uris = await computeFollowerUris(toAccountFollowers, followersException, t) 27 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
30 28
31 if (uris.length === 0) { 29 if (uris.length === 0) {
32 logger.info('0 followers for %s, no forwarding.', toAccountFollowers.map(a => a.id).join(', ')) 30 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
33 return undefined 31 return undefined
34 } 32 }
35 33
@@ -45,14 +43,14 @@ async function forwardActivity (
45 43
46async function broadcastToFollowers ( 44async function broadcastToFollowers (
47 data: any, 45 data: any,
48 byAccount: AccountModel, 46 byActor: ActorModel,
49 toAccountFollowers: AccountModel[], 47 toActorFollowers: ActorModel[],
50 t: Transaction, 48 t: Transaction,
51 followersException: AccountModel[] = [] 49 followersException: ActorModel[] = []
52) { 50) {
53 const uris = await computeFollowerUris(toAccountFollowers, followersException, t) 51 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
54 if (uris.length === 0) { 52 if (uris.length === 0) {
55 logger.info('0 followers for %s, no broadcasting.', toAccountFollowers.map(a => a.id).join(', ')) 53 logger.info('0 followers for %s, no broadcasting.', toActorFollowers.map(a => a.id).join(', '))
56 return undefined 54 return undefined
57 } 55 }
58 56
@@ -60,62 +58,48 @@ async function broadcastToFollowers (
60 58
61 const jobPayload: ActivityPubHttpPayload = { 59 const jobPayload: ActivityPubHttpPayload = {
62 uris, 60 uris,
63 signatureAccountId: byAccount.id, 61 signatureActorId: byActor.id,
64 body: data 62 body: data
65 } 63 }
66 64
67 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload) 65 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
68} 66}
69 67
70async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) { 68async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string, t: Transaction) {
71 logger.debug('Creating unicast job.', { uri: toAccountUrl }) 69 logger.debug('Creating unicast job.', { uri: toActorUrl })
72 70
73 const jobPayload: ActivityPubHttpPayload = { 71 const jobPayload: ActivityPubHttpPayload = {
74 uris: [ toAccountUrl ], 72 uris: [ toActorUrl ],
75 signatureAccountId: byAccount.id, 73 signatureActorId: byActor.id,
76 body: data 74 body: data
77 } 75 }
78 76
79 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload) 77 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
80} 78}
81 79
82function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) { 80function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
83 return { 81 return {
84 to: [ video.VideoChannel.Account.url ], 82 to: [ video.VideoChannel.Account.Actor.url ],
85 cc: accountsInvolvedInVideo.map(a => a.followersUrl) 83 cc: actorsInvolvedInVideo.map(a => a.followersUrl)
86 } 84 }
87} 85}
88 86
89function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) { 87function getObjectFollowersAudience (actorsInvolvedInObject: ActorModel[]) {
90 return { 88 return {
91 to: [ videoChannel.Account.url ], 89 to: actorsInvolvedInObject.map(a => a.followersUrl),
92 cc: accountsInvolved.map(a => a.followersUrl)
93 }
94}
95
96function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) {
97 return {
98 to: accountsInvolvedInObject.map(a => a.followersUrl),
99 cc: [] 90 cc: []
100 } 91 }
101} 92}
102 93
103async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) { 94async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
104 const accountsToForwardView = await VideoShareModel.loadAccountsByShare(video.id, t) 95 const actorsToForwardView = await VideoShareModel.loadActorsByShare(video.id, t)
105 accountsToForwardView.push(video.VideoChannel.Account) 96 actorsToForwardView.push(video.VideoChannel.Account.Actor)
106
107 return accountsToForwardView
108}
109
110async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
111 const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
112 accountsToForwardView.push(videoChannel.Account)
113 97
114 return accountsToForwardView 98 return actorsToForwardView
115} 99}
116 100
117async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) { 101async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) {
118 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t) 102 const followerInboxUrls = await actorSender.getFollowerSharedInboxUrls(t)
119 103
120 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47 104 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
121 let to = [] 105 let to = []
@@ -132,10 +116,10 @@ async function getAudience (accountSender: AccountModel, t: Transaction, isPubli
132 return { to, cc } 116 return { to, cc }
133} 117}
134 118
135async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) { 119async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) {
136 const toAccountFollowerIds = toAccountFollower.map(a => a.id) 120 const toActorFollowerIds = toActorFollower.map(a => a.id)
137 121
138 const result = await AccountFollowModel.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t) 122 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
139 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) 123 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
140 return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) 124 return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
141} 125}
@@ -144,12 +128,10 @@ async function computeFollowerUris (toAccountFollower: AccountModel[], followers
144 128
145export { 129export {
146 broadcastToFollowers, 130 broadcastToFollowers,
147 getOriginVideoChannelAudience,
148 unicastTo, 131 unicastTo,
149 getAudience, 132 getAudience,
150 getOriginVideoAudience, 133 getOriginVideoAudience,
151 getAccountsInvolvedInVideo, 134 getActorsInvolvedInVideo,
152 getAccountsInvolvedInVideoChannel,
153 getObjectFollowersAudience, 135 getObjectFollowersAudience,
154 forwardActivity 136 forwardActivity
155} 137}
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index f160af3c9..7579884a7 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -1,15 +1,15 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAccept } from '../../../../shared/models/activitypub' 2import { ActivityAccept } from '../../../../shared/models/activitypub'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { AccountFollowModel } from '../../../models/account/account-follow' 4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
5import { getAccountFollowAcceptActivityPubUrl } from '../url' 5import { getActorFollowAcceptActivityPubUrl } from '../url'
6import { unicastTo } from './misc' 6import { unicastTo } from './misc'
7 7
8async function sendAccept (accountFollow: AccountFollowModel, t: Transaction) { 8async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
9 const follower = accountFollow.AccountFollower 9 const follower = actorFollow.ActorFollower
10 const me = accountFollow.AccountFollowing 10 const me = actorFollow.ActorFollowing
11 11
12 const url = getAccountFollowAcceptActivityPubUrl(accountFollow) 12 const url = getActorFollowAcceptActivityPubUrl(actorFollow)
13 const data = acceptActivityData(url, me) 13 const data = acceptActivityData(url, me)
14 14
15 return unicastTo(data, me, follower.inboxUrl, t) 15 return unicastTo(data, me, follower.inboxUrl, t)
@@ -23,12 +23,10 @@ export {
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26function acceptActivityData (url: string, byAccount: AccountModel) { 26function acceptActivityData (url: string, byActor: ActorModel): ActivityAccept {
27 const activity: ActivityAccept = { 27 return {
28 type: 'Accept', 28 type: 'Accept',
29 id: url, 29 id: url,
30 actor: byAccount.url 30 actor: byActor.url
31 } 31 }
32
33 return activity
34} 32}
diff --git a/server/lib/activitypub/send/send-add.ts b/server/lib/activitypub/send/send-add.ts
deleted file mode 100644
index fd614db75..000000000
--- a/server/lib/activitypub/send/send-add.ts
+++ /dev/null
@@ -1,45 +0,0 @@
1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { AccountModel } from '../../../models/account/account'
5import { VideoModel } from '../../../models/video/video'
6import { broadcastToFollowers, getAudience } from './misc'
7
8async function sendAddVideo (video: VideoModel, t: Transaction) {
9 const byAccount = video.VideoChannel.Account
10
11 const videoObject = video.toActivityPubObject()
12 const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject, t)
13
14 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
15}
16
17async function addActivityData (
18 url: string,
19 byAccount: AccountModel,
20 video: VideoModel,
21 target: string,
22 object: any,
23 t: Transaction
24): Promise<ActivityAdd> {
25 const videoPublic = video.privacy === VideoPrivacy.PUBLIC
26
27 const { to, cc } = await getAudience(byAccount, t, videoPublic)
28
29 return {
30 type: 'Add',
31 id: url,
32 actor: byAccount.url,
33 to,
34 cc,
35 object,
36 target
37 }
38}
39
40// ---------------------------------------------------------------------------
41
42export {
43 addActivityData,
44 sendAddVideo
45}
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index e685323e8..578fbc630 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,88 +1,59 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/index'
3import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' 2import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
4import { AccountModel } from '../../../models/account/account' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel'
7import { getAnnounceActivityPubUrl } from '../url' 6import { getAnnounceActivityPubUrl } from '../url'
8import { 7import {
9 broadcastToFollowers, 8 broadcastToFollowers,
10 getAccountsInvolvedInVideo, 9 getActorsInvolvedInVideo,
11 getAccountsInvolvedInVideoChannel,
12 getAudience, 10 getAudience,
13 getObjectFollowersAudience, 11 getObjectFollowersAudience,
14 getOriginVideoAudience, 12 getOriginVideoAudience,
15 getOriginVideoChannelAudience,
16 unicastTo 13 unicastTo
17} from './misc' 14} from './misc'
18import { addActivityData } from './send-add'
19import { createActivityData } from './send-create' 15import { createActivityData } from './send-create'
20 16
21async function buildVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 17async function buildVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
22 const url = getAnnounceActivityPubUrl(video.url, byAccount) 18 const url = getAnnounceActivityPubUrl(video.url, byActor)
19 const videoObject = video.toActivityPubObject()
23 20
24 const videoChannel = video.VideoChannel 21 const announcedAudience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
25 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject(), t) 22 const announcedActivity = await createActivityData(url, video.VideoChannel.Account.Actor, videoObject, t, announcedAudience)
26 23
27 const accountsToForwardView = await getAccountsInvolvedInVideo(video, t) 24 const accountsToForwardView = await getActorsInvolvedInVideo(video, t)
28 const audience = getObjectFollowersAudience(accountsToForwardView) 25 const audience = getObjectFollowersAudience(accountsToForwardView)
29 return announceActivityData(url, byAccount, announcedActivity, t, audience) 26 return announceActivityData(url, byActor, announcedActivity, t, audience)
30} 27}
31 28
32async function sendVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 29async function sendVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
33 const data = await buildVideoAnnounceToFollowers(byAccount, video, t) 30 const data = await buildVideoAnnounceToFollowers(byActor, video, t)
34 31
35 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 32 return broadcastToFollowers(data, byActor, [ byActor ], t)
36} 33}
37 34
38async function sendVideoAnnounceToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 35async function sendVideoAnnounceToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
39 const url = getAnnounceActivityPubUrl(video.url, byAccount) 36 const url = getAnnounceActivityPubUrl(video.url, byActor)
40 37
41 const videoChannel = video.VideoChannel 38 const videoObject = video.toActivityPubObject()
42 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject(), t) 39 const announcedActivity = await createActivityData(url, video.VideoChannel.Account.Actor, videoObject, t)
43 40
44 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 41 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
45 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 42 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
46 const data = await createActivityData(url, byAccount, announcedActivity, t, audience) 43 const data = await createActivityData(url, byActor, announcedActivity, t, audience)
47 44
48 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t) 45 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
49}
50
51async function buildVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
52 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
53 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
54
55 const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel, t)
56 const audience = getObjectFollowersAudience(accountsToForwardView)
57 return announceActivityData(url, byAccount, announcedActivity, t, audience)
58}
59
60async function sendVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
61 const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t)
62
63 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
64}
65
66async function sendVideoChannelAnnounceToOrigin (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
67 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
68 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
69
70 const accountsInvolvedInVideo = await getAccountsInvolvedInVideoChannel(videoChannel, t)
71 const audience = getOriginVideoChannelAudience(videoChannel, accountsInvolvedInVideo)
72 const data = await createActivityData(url, byAccount, announcedActivity, t, audience)
73
74 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
75} 46}
76 47
77async function announceActivityData ( 48async function announceActivityData (
78 url: string, 49 url: string,
79 byAccount: AccountModel, 50 byActor: ActorModel,
80 object: ActivityCreate | ActivityAdd, 51 object: ActivityCreate,
81 t: Transaction, 52 t: Transaction,
82 audience?: ActivityAudience 53 audience?: ActivityAudience
83): Promise<ActivityAnnounce> { 54): Promise<ActivityAnnounce> {
84 if (!audience) { 55 if (!audience) {
85 audience = await getAudience(byAccount, t) 56 audience = await getAudience(byActor, t)
86 } 57 }
87 58
88 return { 59 return {
@@ -90,7 +61,7 @@ async function announceActivityData (
90 to: audience.to, 61 to: audience.to,
91 cc: audience.cc, 62 cc: audience.cc,
92 id: url, 63 id: url,
93 actor: byAccount.url, 64 actor: byActor.url,
94 object 65 object
95 } 66 }
96} 67}
@@ -99,10 +70,7 @@ async function announceActivityData (
99 70
100export { 71export {
101 sendVideoAnnounceToFollowers, 72 sendVideoAnnounceToFollowers,
102 sendVideoChannelAnnounceToFollowers,
103 sendVideoAnnounceToOrigin, 73 sendVideoAnnounceToOrigin,
104 sendVideoChannelAnnounceToOrigin,
105 announceActivityData, 74 announceActivityData,
106 buildVideoAnnounceToFollowers, 75 buildVideoAnnounceToFollowers
107 buildVideoChannelAnnounceToFollowers
108} 76}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 9fbaa8196..d26c24838 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -1,111 +1,112 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
3import { getServerAccount } from '../../../helpers' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { AccountModel } from '../../../models/account/account' 4import { getServerActor } from '../../../helpers'
5import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
6import { VideoAbuseModel } from '../../../models/video/video-abuse' 7import { VideoAbuseModel } from '../../../models/video/video-abuse'
7import { VideoChannelModel } from '../../../models/video/video-channel'
8import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 8import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
9import { 9import {
10 broadcastToFollowers, 10 broadcastToFollowers,
11 getAccountsInvolvedInVideo, 11 getActorsInvolvedInVideo,
12 getAudience, 12 getAudience,
13 getObjectFollowersAudience, 13 getObjectFollowersAudience,
14 getOriginVideoAudience, 14 getOriginVideoAudience,
15 unicastTo 15 unicastTo
16} from './misc' 16} from './misc'
17 17
18async function sendCreateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { 18async function sendCreateVideo (video: VideoModel, t: Transaction) {
19 const byAccount = videoChannel.Account 19 const byActor = video.VideoChannel.Account.Actor
20 20
21 const videoChannelObject = videoChannel.toActivityPubObject() 21 const videoObject = video.toActivityPubObject()
22 const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject, t) 22 const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
23 const data = await createActivityData(video.url, byActor, videoObject, t, audience)
23 24
24 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 25 return broadcastToFollowers(data, byActor, [ byActor ], t)
25} 26}
26 27
27async function sendVideoAbuse (byAccount: AccountModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { 28async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
28 const url = getVideoAbuseActivityPubUrl(videoAbuse) 29 const url = getVideoAbuseActivityPubUrl(videoAbuse)
29 30
30 const audience = { to: [ video.VideoChannel.Account.url ], cc: [] } 31 const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
31 const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject(), t, audience) 32 const data = await createActivityData(url, byActor, videoAbuse.toActivityPubObject(), t, audience)
32 33
33 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 34 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
34} 35}
35 36
36async function sendCreateViewToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 37async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
37 const url = getVideoViewActivityPubUrl(byAccount, video) 38 const url = getVideoViewActivityPubUrl(byActor, video)
38 const viewActivity = createViewActivityData(byAccount, video) 39 const viewActivity = createViewActivityData(byActor, video)
39 40
40 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 41 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
41 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 42 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
42 const data = await createActivityData(url, byAccount, viewActivity, t, audience) 43 const data = await createActivityData(url, byActor, viewActivity, t, audience)
43 44
44 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 45 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
45} 46}
46 47
47async function sendCreateViewToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 48async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
48 const url = getVideoViewActivityPubUrl(byAccount, video) 49 const url = getVideoViewActivityPubUrl(byActor, video)
49 const viewActivity = createViewActivityData(byAccount, video) 50 const viewActivity = createViewActivityData(byActor, video)
50 51
51 const accountsToForwardView = await getAccountsInvolvedInVideo(video, t) 52 const actorsToForwardView = await getActorsInvolvedInVideo(video, t)
52 const audience = getObjectFollowersAudience(accountsToForwardView) 53 const audience = getObjectFollowersAudience(actorsToForwardView)
53 const data = await createActivityData(url, byAccount, viewActivity, t, audience) 54 const data = await createActivityData(url, byActor, viewActivity, t, audience)
54 55
55 // Use the server account to send the view, because it could be an unregistered account 56 // Use the server actor to send the view
56 const serverAccount = await getServerAccount() 57 const serverActor = await getServerActor()
57 const followersException = [ byAccount ] 58 const followersException = [ byActor ]
58 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 59 return broadcastToFollowers(data, serverActor, actorsToForwardView, t, followersException)
59} 60}
60 61
61async function sendCreateDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 62async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
62 const url = getVideoDislikeActivityPubUrl(byAccount, video) 63 const url = getVideoDislikeActivityPubUrl(byActor, video)
63 const dislikeActivity = createDislikeActivityData(byAccount, video) 64 const dislikeActivity = createDislikeActivityData(byActor, video)
64 65
65 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 66 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
66 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 67 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
67 const data = await createActivityData(url, byAccount, dislikeActivity, t, audience) 68 const data = await createActivityData(url, byActor, dislikeActivity, t, audience)
68 69
69 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 70 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
70} 71}
71 72
72async function sendCreateDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 73async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
73 const url = getVideoDislikeActivityPubUrl(byAccount, video) 74 const url = getVideoDislikeActivityPubUrl(byActor, video)
74 const dislikeActivity = createDislikeActivityData(byAccount, video) 75 const dislikeActivity = createDislikeActivityData(byActor, video)
75 76
76 const accountsToForwardView = await getAccountsInvolvedInVideo(video, t) 77 const actorsToForwardView = await getActorsInvolvedInVideo(video, t)
77 const audience = getObjectFollowersAudience(accountsToForwardView) 78 const audience = getObjectFollowersAudience(actorsToForwardView)
78 const data = await createActivityData(url, byAccount, dislikeActivity, t, audience) 79 const data = await createActivityData(url, byActor, dislikeActivity, t, audience)
79 80
80 const followersException = [ byAccount ] 81 const followersException = [ byActor ]
81 return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException) 82 return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException)
82} 83}
83 84
84async function createActivityData ( 85async function createActivityData (
85 url: string, 86 url: string,
86 byAccount: AccountModel, 87 byActor: ActorModel,
87 object: any, 88 object: any,
88 t: Transaction, 89 t: Transaction,
89 audience?: ActivityAudience 90 audience?: ActivityAudience
90): Promise<ActivityCreate> { 91): Promise<ActivityCreate> {
91 if (!audience) { 92 if (!audience) {
92 audience = await getAudience(byAccount, t) 93 audience = await getAudience(byActor, t)
93 } 94 }
94 95
95 return { 96 return {
96 type: 'Create', 97 type: 'Create',
97 id: url, 98 id: url,
98 actor: byAccount.url, 99 actor: byActor.url,
99 to: audience.to, 100 to: audience.to,
100 cc: audience.cc, 101 cc: audience.cc,
101 object 102 object
102 } 103 }
103} 104}
104 105
105function createDislikeActivityData (byAccount: AccountModel, video: VideoModel) { 106function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
106 return { 107 return {
107 type: 'Dislike', 108 type: 'Dislike',
108 actor: byAccount.url, 109 actor: byActor.url,
109 object: video.url 110 object: video.url
110 } 111 }
111} 112}
@@ -113,7 +114,7 @@ function createDislikeActivityData (byAccount: AccountModel, video: VideoModel)
113// --------------------------------------------------------------------------- 114// ---------------------------------------------------------------------------
114 115
115export { 116export {
116 sendCreateVideoChannel, 117 sendCreateVideo,
117 sendVideoAbuse, 118 sendVideoAbuse,
118 createActivityData, 119 createActivityData,
119 sendCreateViewToOrigin, 120 sendCreateViewToOrigin,
@@ -125,10 +126,10 @@ export {
125 126
126// --------------------------------------------------------------------------- 127// ---------------------------------------------------------------------------
127 128
128function createViewActivityData (byAccount: AccountModel, video: VideoModel) { 129function createViewActivityData (byActor: ActorModel, video: VideoModel) {
129 return { 130 return {
130 type: 'View', 131 type: 'View',
131 actor: byAccount.url, 132 actor: byActor.url,
132 object: video.url 133 object: video.url
133 } 134 }
134} 135}
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 0a45ea10f..4bc5db77e 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -1,54 +1,40 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityDelete } from '../../../../shared/models/activitypub' 2import { ActivityDelete } from '../../../../shared/models/activitypub'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
5import { VideoChannelModel } from '../../../models/video/video-channel'
6import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
7import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
8import { broadcastToFollowers } from './misc' 6import { broadcastToFollowers } from './misc'
9 7
10async function sendDeleteVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
11 const byAccount = videoChannel.Account
12
13 const data = deleteActivityData(videoChannel.url, byAccount)
14
15 const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
16 accountsInvolved.push(byAccount)
17
18 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
19}
20
21async function sendDeleteVideo (video: VideoModel, t: Transaction) { 8async function sendDeleteVideo (video: VideoModel, t: Transaction) {
22 const byAccount = video.VideoChannel.Account 9 const byActor = video.VideoChannel.Account.Actor
23 10
24 const data = deleteActivityData(video.url, byAccount) 11 const data = deleteActivityData(video.url, byActor)
25 12
26 const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t) 13 const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t)
27 accountsInvolved.push(byAccount) 14 actorsInvolved.push(byActor)
28 15
29 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 16 return broadcastToFollowers(data, byActor, actorsInvolved, t)
30} 17}
31 18
32async function sendDeleteAccount (account: AccountModel, t: Transaction) { 19async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
33 const data = deleteActivityData(account.url, account) 20 const data = deleteActivityData(byActor.url, byActor)
34 21
35 return broadcastToFollowers(data, account, [ account ], t) 22 return broadcastToFollowers(data, byActor, [ byActor ], t)
36} 23}
37 24
38// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
39 26
40export { 27export {
41 sendDeleteVideoChannel,
42 sendDeleteVideo, 28 sendDeleteVideo,
43 sendDeleteAccount 29 sendDeleteActor
44} 30}
45 31
46// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
47 33
48function deleteActivityData (url: string, byAccount: AccountModel): ActivityDelete { 34function deleteActivityData (url: string, byActor: ActorModel): ActivityDelete {
49 return { 35 return {
50 type: 'Delete', 36 type: 'Delete',
51 id: url, 37 id: url,
52 actor: byAccount.url 38 actor: byActor.url
53 } 39 }
54} 40}
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index 51735ddfd..eac60e94f 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -1,26 +1,26 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityFollow } from '../../../../shared/models/activitypub' 2import { ActivityFollow } from '../../../../shared/models/activitypub'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { AccountFollowModel } from '../../../models/account/account-follow' 4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
5import { getAccountFollowActivityPubUrl } from '../url' 5import { getActorFollowActivityPubUrl } from '../url'
6import { unicastTo } from './misc' 6import { unicastTo } from './misc'
7 7
8function sendFollow (accountFollow: AccountFollowModel, t: Transaction) { 8function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
9 const me = accountFollow.AccountFollower 9 const me = actorFollow.ActorFollower
10 const following = accountFollow.AccountFollowing 10 const following = actorFollow.ActorFollowing
11 11
12 const url = getAccountFollowActivityPubUrl(accountFollow) 12 const url = getActorFollowActivityPubUrl(actorFollow)
13 const data = followActivityData(url, me, following) 13 const data = followActivityData(url, me, following)
14 14
15 return unicastTo(data, me, following.inboxUrl, t) 15 return unicastTo(data, me, following.inboxUrl, t)
16} 16}
17 17
18function followActivityData (url: string, byAccount: AccountModel, targetAccount: AccountModel): ActivityFollow { 18function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow {
19 return { 19 return {
20 type: 'Follow', 20 type: 'Follow',
21 id: url, 21 id: url,
22 actor: byAccount.url, 22 actor: byActor.url,
23 object: targetAccount.url 23 object: targetActor.url
24 } 24 }
25} 25}
26 26
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 1a35d0db0..7e0c73796 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -1,53 +1,53 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 5import { getVideoLikeActivityPubUrl } from '../url'
6import { 6import {
7 broadcastToFollowers, 7 broadcastToFollowers,
8 getAccountsInvolvedInVideo, 8 getActorsInvolvedInVideo,
9 getAudience, 9 getAudience,
10 getOriginVideoAudience,
11 getObjectFollowersAudience, 10 getObjectFollowersAudience,
11 getOriginVideoAudience,
12 unicastTo 12 unicastTo
13} from './misc' 13} from './misc'
14 14
15async function sendLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 15async function sendLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
16 const url = getVideoLikeActivityPubUrl(byAccount, video) 16 const url = getVideoLikeActivityPubUrl(byActor, video)
17 17
18 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 18 const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
19 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 19 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
20 const data = await likeActivityData(url, byAccount, video, t, audience) 20 const data = await likeActivityData(url, byActor, video, t, audience)
21 21
22 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 22 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
23} 23}
24 24
25async function sendLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 25async function sendLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
26 const url = getVideoLikeActivityPubUrl(byAccount, video) 26 const url = getVideoLikeActivityPubUrl(byActor, video)
27 27
28 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 28 const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
29 const audience = getObjectFollowersAudience(accountsInvolvedInVideo) 29 const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
30 const data = await likeActivityData(url, byAccount, video, t, audience) 30 const data = await likeActivityData(url, byActor, video, t, audience)
31 31
32 const followersException = [ byAccount ] 32 const followersException = [ byActor ]
33 return broadcastToFollowers(data, byAccount, accountsInvolvedInVideo, t, followersException) 33 return broadcastToFollowers(data, byActor, accountsInvolvedInVideo, t, followersException)
34} 34}
35 35
36async function likeActivityData ( 36async function likeActivityData (
37 url: string, 37 url: string,
38 byAccount: AccountModel, 38 byActor: ActorModel,
39 video: VideoModel, 39 video: VideoModel,
40 t: Transaction, 40 t: Transaction,
41 audience?: ActivityAudience 41 audience?: ActivityAudience
42): Promise<ActivityLike> { 42): Promise<ActivityLike> {
43 if (!audience) { 43 if (!audience) {
44 audience = await getAudience(byAccount, t) 44 audience = await getAudience(byActor, t)
45 } 45 }
46 46
47 return { 47 return {
48 type: 'Like', 48 type: 'Like',
49 id: url, 49 id: url,
50 actor: byAccount.url, 50 actor: byActor.url,
51 to: audience.to, 51 to: audience.to,
52 cc: audience.cc, 52 cc: audience.cc,
53 object: video.url 53 object: video.url
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 699f920f0..92271b700 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -6,13 +6,13 @@ import {
6 ActivityLike, 6 ActivityLike,
7 ActivityUndo 7 ActivityUndo
8} from '../../../../shared/models/activitypub' 8} from '../../../../shared/models/activitypub'
9import { AccountModel } from '../../../models/account/account' 9import { ActorModel } from '../../../models/activitypub/actor'
10import { AccountFollowModel } from '../../../models/account/account-follow' 10import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
11import { VideoModel } from '../../../models/video/video' 11import { VideoModel } from '../../../models/video/video'
12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
13import { 13import {
14 broadcastToFollowers, 14 broadcastToFollowers,
15 getAccountsInvolvedInVideo, 15 getActorsInvolvedInVideo,
16 getAudience, 16 getAudience,
17 getObjectFollowersAudience, 17 getObjectFollowersAudience,
18 getOriginVideoAudience, 18 getOriginVideoAudience,
@@ -22,11 +22,11 @@ import { createActivityData, createDislikeActivityData } from './send-create'
22import { followActivityData } from './send-follow' 22import { followActivityData } from './send-follow'
23import { likeActivityData } from './send-like' 23import { likeActivityData } from './send-like'
24 24
25async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction) { 25async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
26 const me = accountFollow.AccountFollower 26 const me = actorFollow.ActorFollower
27 const following = accountFollow.AccountFollowing 27 const following = actorFollow.ActorFollowing
28 28
29 const followUrl = getAccountFollowActivityPubUrl(accountFollow) 29 const followUrl = getActorFollowActivityPubUrl(actorFollow)
30 const undoUrl = getUndoActivityPubUrl(followUrl) 30 const undoUrl = getUndoActivityPubUrl(followUrl)
31 31
32 const object = followActivityData(followUrl, me, following) 32 const object = followActivityData(followUrl, me, following)
@@ -35,58 +35,58 @@ async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction
35 return unicastTo(data, me, following.inboxUrl, t) 35 return unicastTo(data, me, following.inboxUrl, t)
36} 36}
37 37
38async function sendUndoLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 38async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
39 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) 39 const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
40 const undoUrl = getUndoActivityPubUrl(likeUrl) 40 const undoUrl = getUndoActivityPubUrl(likeUrl)
41 41
42 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 42 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
43 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 43 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
44 const object = await likeActivityData(likeUrl, byAccount, video, t) 44 const object = await likeActivityData(likeUrl, byActor, video, t)
45 const data = await undoActivityData(undoUrl, byAccount, object, t, audience) 45 const data = await undoActivityData(undoUrl, byActor, object, t, audience)
46 46
47 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 47 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
48} 48}
49 49
50async function sendUndoLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 50async function sendUndoLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
51 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) 51 const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
52 const undoUrl = getUndoActivityPubUrl(likeUrl) 52 const undoUrl = getUndoActivityPubUrl(likeUrl)
53 53
54 const toAccountsFollowers = await getAccountsInvolvedInVideo(video, t) 54 const toActorsFollowers = await getActorsInvolvedInVideo(video, t)
55 const audience = getObjectFollowersAudience(toAccountsFollowers) 55 const audience = getObjectFollowersAudience(toActorsFollowers)
56 const object = await likeActivityData(likeUrl, byAccount, video, t) 56 const object = await likeActivityData(likeUrl, byActor, video, t)
57 const data = await undoActivityData(undoUrl, byAccount, object, t, audience) 57 const data = await undoActivityData(undoUrl, byActor, object, t, audience)
58 58
59 const followersException = [ byAccount ] 59 const followersException = [ byActor ]
60 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException) 60 return broadcastToFollowers(data, byActor, toActorsFollowers, t, followersException)
61} 61}
62 62
63async function sendUndoDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { 63async function sendUndoDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
64 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) 64 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
65 const undoUrl = getUndoActivityPubUrl(dislikeUrl) 65 const undoUrl = getUndoActivityPubUrl(dislikeUrl)
66 66
67 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 67 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
68 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 68 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
69 const dislikeActivity = createDislikeActivityData(byAccount, video) 69 const dislikeActivity = createDislikeActivityData(byActor, video)
70 const object = await createActivityData(undoUrl, byAccount, dislikeActivity, t) 70 const object = await createActivityData(undoUrl, byActor, dislikeActivity, t)
71 71
72 const data = await undoActivityData(undoUrl, byAccount, object, t, audience) 72 const data = await undoActivityData(undoUrl, byActor, object, t, audience)
73 73
74 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 74 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t)
75} 75}
76 76
77async function sendUndoDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { 77async function sendUndoDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
78 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) 78 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
79 const undoUrl = getUndoActivityPubUrl(dislikeUrl) 79 const undoUrl = getUndoActivityPubUrl(dislikeUrl)
80 80
81 const dislikeActivity = createDislikeActivityData(byAccount, video) 81 const dislikeActivity = createDislikeActivityData(byActor, video)
82 const object = await createActivityData(undoUrl, byAccount, dislikeActivity, t) 82 const object = await createActivityData(undoUrl, byActor, dislikeActivity, t)
83 83
84 const data = await undoActivityData(undoUrl, byAccount, object, t) 84 const data = await undoActivityData(undoUrl, byActor, object, t)
85 85
86 const toAccountsFollowers = await getAccountsInvolvedInVideo(video, t) 86 const toActorsFollowers = await getActorsInvolvedInVideo(video, t)
87 87
88 const followersException = [ byAccount ] 88 const followersException = [ byActor ]
89 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException) 89 return broadcastToFollowers(data, byActor, toActorsFollowers, t, followersException)
90} 90}
91 91
92// --------------------------------------------------------------------------- 92// ---------------------------------------------------------------------------
@@ -103,19 +103,19 @@ export {
103 103
104async function undoActivityData ( 104async function undoActivityData (
105 url: string, 105 url: string,
106 byAccount: AccountModel, 106 byActor: ActorModel,
107 object: ActivityFollow | ActivityLike | ActivityCreate, 107 object: ActivityFollow | ActivityLike | ActivityCreate,
108 t: Transaction, 108 t: Transaction,
109 audience?: ActivityAudience 109 audience?: ActivityAudience
110): Promise<ActivityUndo> { 110): Promise<ActivityUndo> {
111 if (!audience) { 111 if (!audience) {
112 audience = await getAudience(byAccount, t) 112 audience = await getAudience(byActor, t)
113 } 113 }
114 114
115 return { 115 return {
116 type: 'Undo', 116 type: 'Undo',
117 id: url, 117 id: url,
118 actor: byAccount.url, 118 actor: byActor.url,
119 to: audience.to, 119 to: audience.to,
120 cc: audience.cc, 120 cc: audience.cc,
121 object 121 object
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 9baf13a87..48bbbcac1 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -1,56 +1,52 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityUpdate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
3import { AccountModel } from '../../../models/account/account' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
5import { VideoChannelModel } from '../../../models/video/video-channel'
6import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
7import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
8import { getUpdateActivityPubUrl } from '../url' 7import { getUpdateActivityPubUrl } from '../url'
9import { broadcastToFollowers, getAudience } from './misc' 8import { broadcastToFollowers, getAudience } from './misc'
10 9
11async function sendUpdateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
12 const byAccount = videoChannel.Account
13
14 const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString())
15 const videoChannelObject = videoChannel.toActivityPubObject()
16 const data = await updateActivityData(url, byAccount, videoChannelObject, t)
17
18 const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
19 accountsInvolved.push(byAccount)
20
21 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
22}
23
24async function sendUpdateVideo (video: VideoModel, t: Transaction) { 10async function sendUpdateVideo (video: VideoModel, t: Transaction) {
25 const byAccount = video.VideoChannel.Account 11 const byActor = video.VideoChannel.Account.Actor
26 12
27 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 13 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
28 const videoObject = video.toActivityPubObject() 14 const videoObject = video.toActivityPubObject()
29 const data = await updateActivityData(url, byAccount, videoObject, t) 15 const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
16
17 const data = await updateActivityData(url, byActor, videoObject, t, audience)
30 18
31 const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t) 19 const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t)
32 accountsInvolved.push(byAccount) 20 actorsInvolved.push(byActor)
33 21
34 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 22 return broadcastToFollowers(data, byActor, actorsInvolved, t)
35} 23}
36 24
37// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
38 26
39export { 27export {
40 sendUpdateVideoChannel,
41 sendUpdateVideo 28 sendUpdateVideo
42} 29}
43 30
44// --------------------------------------------------------------------------- 31// ---------------------------------------------------------------------------
45 32
46async function updateActivityData (url: string, byAccount: AccountModel, object: any, t: Transaction): Promise<ActivityUpdate> { 33async function updateActivityData (
47 const { to, cc } = await getAudience(byAccount, t) 34 url: string,
35 byActor: ActorModel,
36 object: any,
37 t: Transaction,
38 audience?: ActivityAudience
39): Promise<ActivityUpdate> {
40 if (!audience) {
41 audience = await getAudience(byActor, t)
42 }
43
48 return { 44 return {
49 type: 'Update', 45 type: 'Update',
50 id: url, 46 id: url,
51 actor: byAccount.url, 47 actor: byActor.url,
52 to, 48 to: audience.to,
53 cc, 49 cc: audience.cc,
54 object 50 object
55 } 51 }
56} 52}
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index 5bec61c05..f79c4e532 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -1,34 +1,20 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { getServerAccount } from '../../helpers' 2import { getServerActor } from '../../helpers'
3import { VideoModel } from '../../models/video/video' 3import { VideoModel } from '../../models/video/video'
4import { VideoChannelModel } from '../../models/video/video-channel'
5import { VideoChannelShareModel } from '../../models/video/video-channel-share'
6import { VideoShareModel } from '../../models/video/video-share' 4import { VideoShareModel } from '../../models/video/video-share'
7import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send' 5import { sendVideoAnnounceToFollowers } from './send'
8
9async function shareVideoChannelByServer (videoChannel: VideoChannelModel, t: Transaction) {
10 const serverAccount = await getServerAccount()
11
12 await VideoChannelShareModel.create({
13 accountId: serverAccount.id,
14 videoChannelId: videoChannel.id
15 }, { transaction: t })
16
17 return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t)
18}
19 6
20async function shareVideoByServer (video: VideoModel, t: Transaction) { 7async function shareVideoByServer (video: VideoModel, t: Transaction) {
21 const serverAccount = await getServerAccount() 8 const serverActor = await getServerActor()
22 9
23 await VideoShareModel.create({ 10 await VideoShareModel.create({
24 accountId: serverAccount.id, 11 actorId: serverActor.id,
25 videoId: video.id 12 videoId: video.id
26 }, { transaction: t }) 13 }, { transaction: t })
27 14
28 return sendVideoAnnounceToFollowers(serverAccount, video, t) 15 return sendVideoAnnounceToFollowers(serverActor, video, t)
29} 16}
30 17
31export { 18export {
32 shareVideoChannelByServer,
33 shareVideoByServer 19 shareVideoByServer
34} 20}
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index 00b4e8852..bb2d4d11e 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -1,16 +1,19 @@
1import { CONFIG } from '../../initializers' 1import { CONFIG } from '../../initializers'
2import { AccountModel } from '../../models/account/account' 2import { ActorModel } from '../../models/activitypub/actor'
3import { AccountFollowModel } from '../../models/account/account-follow' 3import { ActorFollowModel } from '../../models/activitypub/actor-follow'
4import { VideoModel } from '../../models/video/video' 4import { VideoModel } from '../../models/video/video'
5import { VideoAbuseModel } from '../../models/video/video-abuse' 5import { VideoAbuseModel } from '../../models/video/video-abuse'
6import { VideoChannelModel } from '../../models/video/video-channel'
7 6
8function getVideoActivityPubUrl (video: VideoModel) { 7function getVideoActivityPubUrl (video: VideoModel) {
9 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid 8 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
10} 9}
11 10
12function getVideoChannelActivityPubUrl (videoChannel: VideoChannelModel) { 11function getVideoChannelActivityPubUrl (videoChannelUUID: string) {
13 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid 12 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID
13}
14
15function getApplicationActivityPubUrl () {
16 return CONFIG.WEBSERVER.URL + '/application/peertube'
14} 17}
15 18
16function getAccountActivityPubUrl (accountName: string) { 19function getAccountActivityPubUrl (accountName: string) {
@@ -21,34 +24,34 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
21 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id 24 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
22} 25}
23 26
24function getVideoViewActivityPubUrl (byAccount: AccountModel, video: VideoModel) { 27function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) {
25 return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString() 28 return video.url + '/views/' + byActor.uuid + '/' + new Date().toISOString()
26} 29}
27 30
28function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { 31function getVideoLikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
29 return byAccount.url + '/likes/' + video.id 32 return byActor.url + '/likes/' + video.id
30} 33}
31 34
32function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { 35function getVideoDislikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
33 return byAccount.url + '/dislikes/' + video.id 36 return byActor.url + '/dislikes/' + video.id
34} 37}
35 38
36function getAccountFollowActivityPubUrl (accountFollow: AccountFollowModel) { 39function getActorFollowActivityPubUrl (actorFollow: ActorFollowModel) {
37 const me = accountFollow.AccountFollower 40 const me = actorFollow.ActorFollower
38 const following = accountFollow.AccountFollowing 41 const following = actorFollow.ActorFollowing
39 42
40 return me.url + '/follows/' + following.id 43 return me.url + '/follows/' + following.id
41} 44}
42 45
43function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowModel) { 46function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) {
44 const follower = accountFollow.AccountFollower 47 const follower = actorFollow.ActorFollower
45 const me = accountFollow.AccountFollowing 48 const me = actorFollow.ActorFollowing
46 49
47 return follower.url + '/accepts/follows/' + me.id 50 return follower.url + '/accepts/follows/' + me.id
48} 51}
49 52
50function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountModel) { 53function getAnnounceActivityPubUrl (originalUrl: string, byActor: ActorModel) {
51 return originalUrl + '/announces/' + byAccount.id 54 return originalUrl + '/announces/' + byActor.id
52} 55}
53 56
54function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { 57function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
@@ -60,12 +63,13 @@ function getUndoActivityPubUrl (originalUrl: string) {
60} 63}
61 64
62export { 65export {
66 getApplicationActivityPubUrl,
63 getVideoActivityPubUrl, 67 getVideoActivityPubUrl,
64 getVideoChannelActivityPubUrl, 68 getVideoChannelActivityPubUrl,
65 getAccountActivityPubUrl, 69 getAccountActivityPubUrl,
66 getVideoAbuseActivityPubUrl, 70 getVideoAbuseActivityPubUrl,
67 getAccountFollowActivityPubUrl, 71 getActorFollowActivityPubUrl,
68 getAccountFollowAcceptActivityPubUrl, 72 getActorFollowAcceptActivityPubUrl,
69 getAnnounceActivityPubUrl, 73 getAnnounceActivityPubUrl,
70 getUpdateActivityPubUrl, 74 getUpdateActivityPubUrl,
71 getUndoActivityPubUrl, 75 getUndoActivityPubUrl,
diff --git a/server/lib/activitypub/video-channels.ts b/server/lib/activitypub/video-channels.ts
deleted file mode 100644
index c05a46f95..000000000
--- a/server/lib/activitypub/video-channels.ts
+++ /dev/null
@@ -1,59 +0,0 @@
1import { VideoChannelObject } from '../../../shared/models/activitypub/objects'
2import { doRequest, logger } from '../../helpers'
3import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub'
4import { ACTIVITY_PUB } from '../../initializers'
5import { AccountModel } from '../../models/account/account'
6import { VideoChannelModel } from '../../models/video/video-channel'
7import { videoChannelActivityObjectToDBAttributes } from './process/misc'
8
9async function getOrCreateVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
10 let videoChannel = await VideoChannelModel.loadByUrl(videoChannelUrl)
11
12 // We don't have this account in our database, fetch it on remote
13 if (!videoChannel) {
14 videoChannel = await fetchRemoteVideoChannel(ownerAccount, videoChannelUrl)
15 if (videoChannel === undefined) throw new Error('Cannot fetch remote video channel.')
16
17 // Save our new video channel in database
18 await videoChannel.save()
19 }
20
21 return videoChannel
22}
23
24async function fetchRemoteVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
25 const options = {
26 uri: videoChannelUrl,
27 method: 'GET',
28 headers: {
29 'Accept': ACTIVITY_PUB.ACCEPT_HEADER
30 }
31 }
32
33 logger.info('Fetching remote video channel %s.', videoChannelUrl)
34
35 let requestResult
36 try {
37 requestResult = await doRequest(options)
38 } catch (err) {
39 logger.warn('Cannot fetch remote video channel %s.', videoChannelUrl, err)
40 return undefined
41 }
42
43 const videoChannelJSON: VideoChannelObject = JSON.parse(requestResult.body)
44 if (isVideoChannelObjectValid(videoChannelJSON) === false) {
45 logger.debug('Remote video channel JSON is not valid.', { videoChannelJSON })
46 return undefined
47 }
48
49 const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount)
50 const videoChannel = new VideoChannelModel(videoChannelAttributes)
51 videoChannel.Account = ownerAccount
52
53 return videoChannel
54}
55
56export {
57 getOrCreateVideoChannel,
58 fetchRemoteVideoChannel
59}
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 14c07fec0..fab43757a 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -19,7 +19,7 @@ import {
19 19
20function fetchRemoteVideoPreview (video: VideoModel) { 20function fetchRemoteVideoPreview (video: VideoModel) {
21 // FIXME: use url 21 // FIXME: use url
22 const host = video.VideoChannel.Account.Server.host 22 const host = video.VideoChannel.Account.Actor.Server.host
23 const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) 23 const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
24 24
25 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) 25 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
@@ -27,7 +27,7 @@ function fetchRemoteVideoPreview (video: VideoModel) {
27 27
28async function fetchRemoteVideoDescription (video: VideoModel) { 28async function fetchRemoteVideoDescription (video: VideoModel) {
29 // FIXME: use url 29 // FIXME: use url
30 const host = video.VideoChannel.Account.Server.host 30 const host = video.VideoChannel.Account.Actor.Server.host
31 const path = video.getDescriptionPath() 31 const path = video.getDescriptionPath()
32 const options = { 32 const options = {
33 uri: REMOTE_SCHEME.HTTP + '://' + host + path, 33 uri: REMOTE_SCHEME.HTTP + '://' + host + path,
@@ -50,43 +50,47 @@ function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject)
50} 50}
51 51
52async function sendVideoRateChangeToFollowers ( 52async function sendVideoRateChangeToFollowers (
53 account: AccountModel, 53 account: AccountModel,
54 video: VideoModel, 54 video: VideoModel,
55 likes: number, 55 likes: number,
56 dislikes: number, 56 dislikes: number,
57 t: Transaction 57 t: Transaction
58) { 58) {
59 const actor = account.Actor
60
59 // Keep the order: first we undo and then we create 61 // Keep the order: first we undo and then we create
60 62
61 // Undo Like 63 // Undo Like
62 if (likes < 0) await sendUndoLikeToVideoFollowers(account, video, t) 64 if (likes < 0) await sendUndoLikeToVideoFollowers(actor, video, t)
63 // Undo Dislike 65 // Undo Dislike
64 if (dislikes < 0) await sendUndoDislikeToVideoFollowers(account, video, t) 66 if (dislikes < 0) await sendUndoDislikeToVideoFollowers(actor, video, t)
65 67
66 // Like 68 // Like
67 if (likes > 0) await sendLikeToVideoFollowers(account, video, t) 69 if (likes > 0) await sendLikeToVideoFollowers(actor, video, t)
68 // Dislike 70 // Dislike
69 if (dislikes > 0) await sendCreateDislikeToVideoFollowers(account, video, t) 71 if (dislikes > 0) await sendCreateDislikeToVideoFollowers(actor, video, t)
70} 72}
71 73
72async function sendVideoRateChangeToOrigin ( 74async function sendVideoRateChangeToOrigin (
73 account: AccountModel, 75 account: AccountModel,
74 video: VideoModel, 76 video: VideoModel,
75 likes: number, 77 likes: number,
76 dislikes: number, 78 dislikes: number,
77 t: Transaction 79 t: Transaction
78) { 80) {
81 const actor = account.Actor
82
79 // Keep the order: first we undo and then we create 83 // Keep the order: first we undo and then we create
80 84
81 // Undo Like 85 // Undo Like
82 if (likes < 0) await sendUndoLikeToOrigin(account, video, t) 86 if (likes < 0) await sendUndoLikeToOrigin(actor, video, t)
83 // Undo Dislike 87 // Undo Dislike
84 if (dislikes < 0) await sendUndoDislikeToOrigin(account, video, t) 88 if (dislikes < 0) await sendUndoDislikeToOrigin(actor, video, t)
85 89
86 // Like 90 // Like
87 if (likes > 0) await sendLikeToOrigin(account, video, t) 91 if (likes > 0) await sendLikeToOrigin(actor, video, t)
88 // Dislike 92 // Dislike
89 if (dislikes > 0) await sendCreateDislikeToOrigin(account, video, t) 93 if (dislikes > 0) await sendCreateDislikeToOrigin(actor, video, t)
90} 94}
91 95
92export { 96export {
diff --git a/server/lib/index.ts b/server/lib/index.ts
deleted file mode 100644
index d22ecb665..000000000
--- a/server/lib/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
1export * from './activitypub'
2export * from './cache'
3export * from './jobs'
4export * from './oauth-model'
5export * from './user'
6export * from './video-channel'
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
index 76da5b724..95a5d3ff2 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
@@ -1,7 +1,7 @@
1import { JobCategory } from '../../../../shared' 1import { JobCategory } from '../../../../shared'
2import { buildSignedActivity, logger } from '../../../helpers' 2import { buildSignedActivity, logger } from '../../../helpers'
3import { ACTIVITY_PUB } from '../../../initializers' 3import { ACTIVITY_PUB } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { JobHandler, JobScheduler } from '../job-scheduler' 5import { JobHandler, JobScheduler } from '../job-scheduler'
6 6
7import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' 7import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
@@ -10,7 +10,7 @@ import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handl
10 10
11type ActivityPubHttpPayload = { 11type ActivityPubHttpPayload = {
12 uris: string[] 12 uris: string[]
13 signatureAccountId?: number 13 signatureActorId?: number
14 body?: any 14 body?: any
15 attemptNumber?: number 15 attemptNumber?: number
16} 16}
@@ -44,10 +44,10 @@ function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, ur
44async function computeBody (payload: ActivityPubHttpPayload) { 44async function computeBody (payload: ActivityPubHttpPayload) {
45 let body = payload.body 45 let body = payload.body
46 46
47 if (payload.signatureAccountId) { 47 if (payload.signatureActorId) {
48 const accountSignature = await AccountModel.load(payload.signatureAccountId) 48 const actorSignature = await ActorModel.load(payload.signatureActorId)
49 if (!accountSignature) throw new Error('Unknown signature account id.') 49 if (!actorSignature) throw new Error('Unknown signature account id.')
50 body = await buildSignedActivity(accountSignature, payload.body) 50 body = await buildSignedActivity(actorSignature, payload.body)
51 } 51 }
52 52
53 return body 53 return body
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
index 1786ce971..7df048006 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
@@ -3,7 +3,7 @@ import { computeResolutionsToTranscode, logger } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
5import { shareVideoByServer } from '../../activitypub' 5import { shareVideoByServer } from '../../activitypub'
6import { sendAddVideo } from '../../activitypub/send' 6import { sendCreateVideo } from '../../activitypub/send'
7import { JobScheduler } from '../job-scheduler' 7import { JobScheduler } from '../job-scheduler'
8import { TranscodingJobPayload } from './transcoding-job-scheduler' 8import { TranscodingJobPayload } from './transcoding-job-scheduler'
9 9
@@ -36,7 +36,8 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
36 if (!videoDatabase) return undefined 36 if (!videoDatabase) return undefined
37 37
38 // Now we'll add the video's meta data to our followers 38 // Now we'll add the video's meta data to our followers
39 await sendAddVideo(video, undefined) 39 await sendCreateVideo(video, undefined)
40 // TODO: share by channel
40 await shareVideoByServer(video, undefined) 41 await shareVideoByServer(video, undefined)
41 42
42 const originalFileHeight = await videoDatabase.getOriginalFileHeight() 43 const originalFileHeight = await videoDatabase.getOriginalFileHeight()
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 6aeb198b9..ec1466c6f 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,10 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { createPrivateAndPublicKeys, logger } from '../helpers' 2import { ActivityPubActorType } from '../../shared/models/activitypub'
3import { CONFIG, sequelizeTypescript } from '../initializers' 3import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../initializers'
4import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
5import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/account/user'
6import { ActorModel } from '../models/activitypub/actor' 6import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
7import { getAccountActivityPubUrl } from './activitypub'
8import { createVideoChannel } from './video-channel' 7import { createVideoChannel } from './video-channel'
9 8
10async function createUserAccountAndChannel (user: UserModel, validateUser = true) { 9async function createUserAccountAndChannel (user: UserModel, validateUser = true) {
@@ -26,31 +25,22 @@ async function createUserAccountAndChannel (user: UserModel, validateUser = true
26 return { account: accountCreated, videoChannel } 25 return { account: accountCreated, videoChannel }
27 }) 26 })
28 27
29 // Set account keys, this could be long so process after the account creation and do not block the client 28 account.Actor = await setAsyncActorKeys(account.Actor)
30 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 29 videoChannel.Actor = await setAsyncActorKeys(videoChannel.Actor)
31 const actor = account.Actor
32 actor.set('publicKey', publicKey)
33 actor.set('privateKey', privateKey)
34 actor.save().catch(err => logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err))
35 30
36 return { account, videoChannel } 31 return { account, videoChannel }
37} 32}
38 33
39async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { 34async function createLocalAccountWithoutKeys (
35 name: string,
36 userId: number,
37 applicationId: number,
38 t: Sequelize.Transaction,
39 type: ActivityPubActorType= 'Person'
40) {
40 const url = getAccountActivityPubUrl(name) 41 const url = getAccountActivityPubUrl(name)
41 42
42 const actorInstance = new ActorModel({ 43 const actorInstance = buildActorInstance(type, url, name)
43 url,
44 publicKey: null,
45 privateKey: null,
46 followersCount: 0,
47 followingCount: 0,
48 inboxUrl: url + '/inbox',
49 outboxUrl: url + '/outbox',
50 sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
51 followersUrl: url + '/followers',
52 followingUrl: url + '/following'
53 })
54 const actorInstanceCreated = await actorInstance.save({ transaction: t }) 44 const actorInstanceCreated = await actorInstance.save({ transaction: t })
55 45
56 const accountInstance = new AccountModel({ 46 const accountInstance = new AccountModel({
@@ -67,9 +57,18 @@ async function createLocalAccountWithoutKeys (name: string, userId: number, appl
67 return accountInstanceCreated 57 return accountInstanceCreated
68} 58}
69 59
60async function createApplicationActor (applicationId: number) {
61 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationId, undefined, 'Application')
62
63 accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor)
64
65 return accountCreated
66}
67
70// --------------------------------------------------------------------------- 68// ---------------------------------------------------------------------------
71 69
72export { 70export {
71 createApplicationActor,
73 createUserAccountAndChannel, 72 createUserAccountAndChannel,
74 createLocalAccountWithoutKeys 73 createLocalAccountWithoutKeys
75} 74}
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 97924aa9e..569b8f29d 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,26 +1,33 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4'
2import { VideoChannelCreate } from '../../shared/models' 3import { VideoChannelCreate } from '../../shared/models'
3import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
4import { VideoChannelModel } from '../models/video/video-channel' 5import { VideoChannelModel } from '../models/video/video-channel'
5import { getVideoChannelActivityPubUrl } from './activitypub' 6import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub'
6 7
7async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { 8async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
9 const uuid = uuidv4()
10 const url = getVideoChannelActivityPubUrl(uuid)
11 // We use the name as uuid
12 const actorInstance = buildActorInstance('Group', url, uuid, uuid)
13
14 const actorInstanceCreated = await actorInstance.save({ transaction: t })
15
8 const videoChannelData = { 16 const videoChannelData = {
9 name: videoChannelInfo.name, 17 name: videoChannelInfo.name,
10 description: videoChannelInfo.description, 18 description: videoChannelInfo.description,
11 remote: false, 19 accountId: account.id,
12 accountId: account.id 20 actorId: actorInstanceCreated.id
13 } 21 }
14 22
15 const videoChannel = VideoChannelModel.build(videoChannelData) 23 const videoChannel = VideoChannelModel.build(videoChannelData)
16 videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel))
17 24
18 const options = { transaction: t } 25 const options = { transaction: t }
19
20 const videoChannelCreated = await videoChannel.save(options) 26 const videoChannelCreated = await videoChannel.save(options)
21 27
22 // Do not forget to add Account information to the created video channel 28 // Do not forget to add Account/Actor information to the created video channel
23 videoChannelCreated.Account = account 29 videoChannelCreated.Account = account
30 videoChannelCreated.Actor = actorInstanceCreated
24 31
25 // No need to seed this empty video channel to followers 32 // No need to seed this empty video channel to followers
26 return videoChannelCreated 33 return videoChannelCreated
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index 489396447..37b7c42ec 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -3,33 +3,27 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'
3import { ActivityPubSignature } from '../../shared' 3import { ActivityPubSignature } from '../../shared'
4import { isSignatureVerified, logger } from '../helpers' 4import { isSignatureVerified, logger } from '../helpers'
5import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers' 5import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers'
6import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub' 6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub'
7import { AccountModel } from '../models/account/account' 7import { ActorModel } from '../models/activitypub/actor'
8 8
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 9async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 const signatureObject: ActivityPubSignature = req.body.signature 10 const signatureObject: ActivityPubSignature = req.body.signature
11 11
12 logger.debug('Checking signature of account %s...', signatureObject.creator) 12 logger.debug('Checking signature of actor %s...', signatureObject.creator)
13 13
14 let account = await AccountModel.loadByUrl(signatureObject.creator) 14 let actor: ActorModel
15 15 try {
16 // We don't have this account in our database, fetch it on remote 16 actor = await getOrCreateActorAndServerAndModel(signatureObject.creator)
17 if (!account) { 17 } catch (err) {
18 account = await fetchRemoteAccount(signatureObject.creator) 18 logger.error('Cannot create remote actor and check signature.', err)
19 19 return res.sendStatus(403)
20 if (!account) {
21 return res.sendStatus(403)
22 }
23
24 // Save our new account and its server in database
25 await saveAccountAndServerIfNotExist(account)
26 } 20 }
27 21
28 const verified = await isSignatureVerified(account, req.body) 22 const verified = await isSignatureVerified(actor, req.body)
29 if (verified === false) return res.sendStatus(403) 23 if (verified === false) return res.sendStatus(403)
30 24
31 res.locals.signature = { 25 res.locals.signature = {
32 account 26 actor
33 } 27 }
34 28
35 return next() 29 return next()
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index 10482e5d0..2240d30db 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -1,10 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator/check' 2import { body, param } from 'express-validator/check'
3import { getServerAccount, isTestInstance, logger } from '../../helpers' 3import { getServerActor, isTestInstance, logger } from '../../helpers'
4import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 4import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers'
5import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
6import { CONFIG } from '../../initializers' 5import { CONFIG } from '../../initializers'
7import { AccountFollowModel } from '../../models/account/account-follow' 6import { ActorFollowModel } from '../../models/activitypub/actor-follow'
8import { areValidationErrors } from './utils' 7import { areValidationErrors } from './utils'
9 8
10const followValidator = [ 9const followValidator = [
@@ -29,15 +28,15 @@ const followValidator = [
29] 28]
30 29
31const removeFollowingValidator = [ 30const removeFollowingValidator = [
32 param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), 31 param('host').custom(isHostValid).withMessage('Should have a valid host'),
33 32
34 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 33 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
35 logger.debug('Checking unfollow parameters', { parameters: req.params }) 34 logger.debug('Checking unfollow parameters', { parameters: req.params })
36 35
37 if (areValidationErrors(req, res)) return 36 if (areValidationErrors(req, res)) return
38 37
39 const serverAccount = await getServerAccount() 38 const serverActor = await getServerActor()
40 const follow = await AccountFollowModel.loadByAccountAndTarget(serverAccount.id, req.params.accountId) 39 const follow = await ActorFollowModel.loadByActorAndTargetHost(serverActor.id, req.params.host)
41 40
42 if (!follow) { 41 if (!follow) {
43 return res.status(404) 42 return res.status(404)
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts
index 068fd210f..cc7d54c06 100644
--- a/server/middlewares/validators/video-channels.ts
+++ b/server/middlewares/validators/video-channels.ts
@@ -3,7 +3,7 @@ import { body, param } from 'express-validator/check'
3import { UserRight } from '../../../shared' 3import { UserRight } from '../../../shared'
4import { logger } from '../../helpers' 4import { logger } from '../../helpers'
5import { isAccountIdExist } from '../../helpers/custom-validators/accounts' 5import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
6import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' 6import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
7import { 7import {
8 isVideoChannelDescriptionValid, 8 isVideoChannelDescriptionValid,
9 isVideoChannelExist, 9 isVideoChannelExist,
@@ -11,7 +11,6 @@ import {
11} from '../../helpers/custom-validators/video-channels' 11} from '../../helpers/custom-validators/video-channels'
12import { UserModel } from '../../models/account/user' 12import { UserModel } from '../../models/account/user'
13import { VideoChannelModel } from '../../models/video/video-channel' 13import { VideoChannelModel } from '../../models/video/video-channel'
14import { VideoChannelShareModel } from '../../models/video/video-channel-share'
15import { areValidationErrors } from './utils' 14import { areValidationErrors } from './utils'
16 15
17const listVideoAccountChannelsValidator = [ 16const listVideoAccountChannelsValidator = [
@@ -98,28 +97,6 @@ const videoChannelsGetValidator = [
98 } 97 }
99] 98]
100 99
101const videoChannelsShareValidator = [
102 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
103 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
104
105 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
106 logger.debug('Checking videoChannelShare parameters', { parameters: req.params })
107
108 if (areValidationErrors(req, res)) return
109 if (!await isVideoChannelExist(req.params.id, res)) return
110
111 const share = await VideoChannelShareModel.load(res.locals.video.id, req.params.accountId, undefined)
112 if (!share) {
113 return res.status(404)
114 .end()
115 }
116
117 res.locals.videoChannelShare = share
118
119 return next()
120 }
121]
122
123// --------------------------------------------------------------------------- 100// ---------------------------------------------------------------------------
124 101
125export { 102export {
@@ -127,15 +104,13 @@ export {
127 videoChannelsAddValidator, 104 videoChannelsAddValidator,
128 videoChannelsUpdateValidator, 105 videoChannelsUpdateValidator,
129 videoChannelsRemoveValidator, 106 videoChannelsRemoveValidator,
130 videoChannelsGetValidator, 107 videoChannelsGetValidator
131 videoChannelsShareValidator
132} 108}
133 109
134// --------------------------------------------------------------------------- 110// ---------------------------------------------------------------------------
135 111
136function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { 112function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
137 // Retrieve the user who did the request 113 if (videoChannel.Actor.isOwned() === false) {
138 if (videoChannel.isOwned() === false) {
139 res.status(403) 114 res.status(403)
140 .json({ error: 'Cannot remove video channel of another server.' }) 115 .json({ error: 'Cannot remove video channel of another server.' })
141 .end() 116 .end()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index 7903c7400..2c8351799 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import { query } from 'express-validator/check' 2import { query } from 'express-validator/check'
3import { logger } from '../../helpers' 3import { logger } from '../../helpers'
4import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' 4import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
5import { AccountModel } from '../../models/account/account' 5import { ActorModel } from '../../models/activitypub/actor'
6import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
7 7
8const webfingerValidator = [ 8const webfingerValidator = [
@@ -17,14 +17,14 @@ const webfingerValidator = [
17 const nameWithHost = req.query.resource.substr(5) 17 const nameWithHost = req.query.resource.substr(5)
18 const [ name ] = nameWithHost.split('@') 18 const [ name ] = nameWithHost.split('@')
19 19
20 const account = await AccountModel.loadLocalByName(name) 20 const actor = await ActorModel.loadLocalByName(name)
21 if (!account) { 21 if (!actor) {
22 return res.status(404) 22 return res.status(404)
23 .send({ error: 'Account not found' }) 23 .send({ error: 'Actor not found' })
24 .end() 24 .end()
25 } 25 }
26 26
27 res.locals.account = account 27 res.locals.actor = actor
28 return next() 28 return next()
29 } 29 }
30] 30]
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
deleted file mode 100644
index 975e7ee7d..000000000
--- a/server/models/account/account-follow.ts
+++ /dev/null
@@ -1,228 +0,0 @@
1import * as Bluebird from 'bluebird'
2import { values } from 'lodash'
3import * as Sequelize from 'sequelize'
4import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
5import { FollowState } from '../../../shared/models/accounts'
6import { FOLLOW_STATES } from '../../initializers/constants'
7import { ServerModel } from '../server/server'
8import { getSort } from '../utils'
9import { AccountModel } from './account'
10
11@Table({
12 tableName: 'accountFollow',
13 indexes: [
14 {
15 fields: [ 'accountId' ]
16 },
17 {
18 fields: [ 'targetAccountId' ]
19 },
20 {
21 fields: [ 'accountId', 'targetAccountId' ],
22 unique: true
23 }
24 ]
25})
26export class AccountFollowModel extends Model<AccountFollowModel> {
27
28 @AllowNull(false)
29 @Column(DataType.ENUM(values(FOLLOW_STATES)))
30 state: FollowState
31
32 @CreatedAt
33 createdAt: Date
34
35 @UpdatedAt
36 updatedAt: Date
37
38 @ForeignKey(() => AccountModel)
39 @Column
40 accountId: number
41
42 @BelongsTo(() => AccountModel, {
43 foreignKey: {
44 name: 'accountId',
45 allowNull: false
46 },
47 as: 'AccountFollower',
48 onDelete: 'CASCADE'
49 })
50 AccountFollower: AccountModel
51
52 @ForeignKey(() => AccountModel)
53 @Column
54 targetAccountId: number
55
56 @BelongsTo(() => AccountModel, {
57 foreignKey: {
58 name: 'targetAccountId',
59 allowNull: false
60 },
61 as: 'AccountFollowing',
62 onDelete: 'CASCADE'
63 })
64 AccountFollowing: AccountModel
65
66 static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
67 const query = {
68 where: {
69 accountId,
70 targetAccountId
71 },
72 include: [
73 {
74 model: AccountModel,
75 required: true,
76 as: 'AccountFollower'
77 },
78 {
79 model: AccountModel,
80 required: true,
81 as: 'AccountFollowing'
82 }
83 ],
84 transaction: t
85 }
86
87 return AccountFollowModel.findOne(query)
88 }
89
90 static listFollowingForApi (id: number, start: number, count: number, sort: string) {
91 const query = {
92 distinct: true,
93 offset: start,
94 limit: count,
95 order: [ getSort(sort) ],
96 include: [
97 {
98 model: AccountModel,
99 required: true,
100 as: 'AccountFollower',
101 where: {
102 id
103 }
104 },
105 {
106 model: AccountModel,
107 as: 'AccountFollowing',
108 required: true,
109 include: [ ServerModel ]
110 }
111 ]
112 }
113
114 return AccountFollowModel.findAndCountAll(query)
115 .then(({ rows, count }) => {
116 return {
117 data: rows,
118 total: count
119 }
120 })
121 }
122
123 static listFollowersForApi (id: number, start: number, count: number, sort: string) {
124 const query = {
125 distinct: true,
126 offset: start,
127 limit: count,
128 order: [ getSort(sort) ],
129 include: [
130 {
131 model: AccountModel,
132 required: true,
133 as: 'AccountFollower',
134 include: [ ServerModel ]
135 },
136 {
137 model: AccountModel,
138 as: 'AccountFollowing',
139 required: true,
140 where: {
141 id
142 }
143 }
144 ]
145 }
146
147 return AccountFollowModel.findAndCountAll(query)
148 .then(({ rows, count }) => {
149 return {
150 data: rows,
151 total: count
152 }
153 })
154 }
155
156 static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
157 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
158 }
159
160 static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) {
161 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
162 }
163
164 static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
165 return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
166 }
167
168 private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
169 accountIds: number[],
170 t: Sequelize.Transaction,
171 start?: number,
172 count?: number,
173 columnUrl = 'url') {
174 let firstJoin: string
175 let secondJoin: string
176
177 if (type === 'followers') {
178 firstJoin = 'targetAccountId'
179 secondJoin = 'accountId'
180 } else {
181 firstJoin = 'accountId'
182 secondJoin = 'targetAccountId'
183 }
184
185 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
186 const tasks: Bluebird<any>[] = []
187
188 for (const selection of selections) {
189 let query = 'SELECT ' + selection + ' FROM "account" ' +
190 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' +
191 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' +
192 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' '
193
194 if (count !== undefined) query += 'LIMIT ' + count
195 if (start !== undefined) query += ' OFFSET ' + start
196
197 const options = {
198 bind: { accountIds },
199 type: Sequelize.QueryTypes.SELECT,
200 transaction: t
201 }
202 tasks.push(AccountFollowModel.sequelize.query(query, options))
203 }
204
205 const [ followers, [ { total } ] ] = await
206 Promise.all(tasks)
207 const urls: string[] = followers.map(f => f.url)
208
209 return {
210 data: urls,
211 total: parseInt(total, 10)
212 }
213 }
214
215 toFormattedJSON () {
216 const follower = this.AccountFollower.toFormattedJSON()
217 const following = this.AccountFollowing.toFormattedJSON()
218
219 return {
220 id: this.id,
221 follower,
222 following,
223 state: this.state,
224 createdAt: this.createdAt,
225 updatedAt: this.updatedAt
226 }
227 }
228}
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index b26395fd4..1ee232537 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -5,18 +5,16 @@ import {
5 BelongsTo, 5 BelongsTo,
6 Column, 6 Column,
7 CreatedAt, 7 CreatedAt,
8 DataType, 8 DefaultScope,
9 Default,
10 ForeignKey, 9 ForeignKey,
11 HasMany, 10 HasMany,
12 Is, 11 Is,
13 IsUUID,
14 Model, 12 Model,
15 Table, 13 Table,
16 UpdatedAt 14 UpdatedAt
17} from 'sequelize-typescript' 15} from 'sequelize-typescript'
18import { isUserUsernameValid } from '../../helpers/custom-validators/users' 16import { isUserUsernameValid } from '../../helpers/custom-validators/users'
19import { sendDeleteAccount } from '../../lib/activitypub/send' 17import { sendDeleteActor } from '../../lib/activitypub/send'
20import { ActorModel } from '../activitypub/actor' 18import { ActorModel } from '../activitypub/actor'
21import { ApplicationModel } from '../application/application' 19import { ApplicationModel } from '../application/application'
22import { ServerModel } from '../server/server' 20import { ServerModel } from '../server/server'
@@ -24,31 +22,30 @@ import { throwIfNotValid } from '../utils'
24import { VideoChannelModel } from '../video/video-channel' 22import { VideoChannelModel } from '../video/video-channel'
25import { UserModel } from './user' 23import { UserModel } from './user'
26 24
27@Table({ 25@DefaultScope({
28 tableName: 'account', 26 include: [
29 indexes: [
30 {
31 fields: [ 'name' ]
32 },
33 {
34 fields: [ 'serverId' ]
35 },
36 {
37 fields: [ 'userId' ],
38 unique: true
39 },
40 {
41 fields: [ 'applicationId' ],
42 unique: true
43 },
44 { 27 {
45 fields: [ 'name', 'serverId', 'applicationId' ], 28 model: () => ActorModel,
46 unique: true 29 required: true,
30 include: [
31 {
32 model: () => ServerModel,
33 required: false
34 }
35 ]
47 } 36 }
48 ] 37 ]
49}) 38})
39@Table({
40 tableName: 'account'
41})
50export class AccountModel extends Model<AccountModel> { 42export class AccountModel extends Model<AccountModel> {
51 43
44 @AllowNull(false)
45 @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
46 @Column
47 name: string
48
52 @CreatedAt 49 @CreatedAt
53 createdAt: Date 50 createdAt: Date
54 51
@@ -89,7 +86,7 @@ export class AccountModel extends Model<AccountModel> {
89 }, 86 },
90 onDelete: 'cascade' 87 onDelete: 'cascade'
91 }) 88 })
92 Application: ApplicationModel 89 Account: ApplicationModel
93 90
94 @HasMany(() => VideoChannelModel, { 91 @HasMany(() => VideoChannelModel, {
95 foreignKey: { 92 foreignKey: {
@@ -103,32 +100,27 @@ export class AccountModel extends Model<AccountModel> {
103 @AfterDestroy 100 @AfterDestroy
104 static sendDeleteIfOwned (instance: AccountModel) { 101 static sendDeleteIfOwned (instance: AccountModel) {
105 if (instance.isOwned()) { 102 if (instance.isOwned()) {
106 return sendDeleteAccount(instance, undefined) 103 return sendDeleteActor(instance.Actor, undefined)
107 } 104 }
108 105
109 return undefined 106 return undefined
110 } 107 }
111 108
112 static loadApplication () {
113 return AccountModel.findOne({
114 include: [
115 {
116 model: ApplicationModel,
117 required: true
118 }
119 ]
120 })
121 }
122
123 static load (id: number) { 109 static load (id: number) {
124 return AccountModel.findById(id) 110 return AccountModel.findById(id)
125 } 111 }
126 112
127 static loadByUUID (uuid: string) { 113 static loadByUUID (uuid: string) {
128 const query = { 114 const query = {
129 where: { 115 include: [
130 uuid 116 {
131 } 117 model: ActorModel,
118 required: true,
119 where: {
120 uuid
121 }
122 }
123 ]
132 } 124 }
133 125
134 return AccountModel.findOne(query) 126 return AccountModel.findOne(query)
@@ -156,25 +148,6 @@ export class AccountModel extends Model<AccountModel> {
156 return AccountModel.findOne(query) 148 return AccountModel.findOne(query)
157 } 149 }
158 150
159 static loadByNameAndHost (name: string, host: string) {
160 const query = {
161 where: {
162 name
163 },
164 include: [
165 {
166 model: ServerModel,
167 required: true,
168 where: {
169 host
170 }
171 }
172 ]
173 }
174
175 return AccountModel.findOne(query)
176 }
177
178 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 151 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
179 const query = { 152 const query = {
180 include: [ 153 include: [
@@ -192,29 +165,11 @@ export class AccountModel extends Model<AccountModel> {
192 return AccountModel.findOne(query) 165 return AccountModel.findOne(query)
193 } 166 }
194 167
195 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
196 const query = {
197 include: [
198 {
199 model: ActorModel,
200 required: true,
201 where: {
202 followersUrl: {
203 [ Sequelize.Op.in ]: followersUrls
204 }
205 }
206 }
207 ],
208 transaction
209 }
210
211 return AccountModel.findAll(query)
212 }
213
214 toFormattedJSON () { 168 toFormattedJSON () {
215 const actor = this.Actor.toFormattedJSON() 169 const actor = this.Actor.toFormattedJSON()
216 const account = { 170 const account = {
217 id: this.id, 171 id: this.id,
172 name: this.name,
218 createdAt: this.createdAt, 173 createdAt: this.createdAt,
219 updatedAt: this.updatedAt 174 updatedAt: this.updatedAt
220 } 175 }
@@ -223,7 +178,7 @@ export class AccountModel extends Model<AccountModel> {
223 } 178 }
224 179
225 toActivityPubObject () { 180 toActivityPubObject () {
226 return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account') 181 return this.Actor.toActivityPubObject(this.name, 'Account')
227 } 182 }
228 183
229 isOwned () { 184 isOwned () {
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 70ed61e07..1d5759ea3 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -1,26 +1,13 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { 2import {
3 AllowNull, 3 AllowNull, BeforeCreate, BeforeUpdate, Column, CreatedAt, DataType, Default, DefaultScope, HasMany, HasOne, Is, IsEmail, Model,
4 BeforeCreate, 4 Scopes, Table, UpdatedAt
5 BeforeUpdate,
6 Column, CreatedAt,
7 DataType,
8 Default, DefaultScope,
9 HasMany,
10 HasOne,
11 Is,
12 IsEmail,
13 Model, Scopes,
14 Table, UpdatedAt
15} from 'sequelize-typescript' 5} from 'sequelize-typescript'
16import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' 6import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
7import { comparePassword, cryptPassword } from '../../helpers'
17import { 8import {
18 comparePassword, 9 isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
19 cryptPassword 10 isUserVideoQuotaValid
20} from '../../helpers'
21import {
22 isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
23 isUserVideoQuotaValid, isUserAutoPlayVideoValid
24} from '../../helpers/custom-validators/users' 11} from '../../helpers/custom-validators/users'
25import { OAuthTokenModel } from '../oauth/oauth-token' 12import { OAuthTokenModel } from '../oauth/oauth-token'
26import { getSort, throwIfNotValid } from '../utils' 13import { getSort, throwIfNotValid } from '../utils'
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts
new file mode 100644
index 000000000..4cba05e95
--- /dev/null
+++ b/server/models/activitypub/actor-follow.ts
@@ -0,0 +1,260 @@
1import * as Bluebird from 'bluebird'
2import { values } from 'lodash'
3import * as Sequelize from 'sequelize'
4import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
5import { FollowState } from '../../../shared/models/actors'
6import { FOLLOW_STATES } from '../../initializers/constants'
7import { ServerModel } from '../server/server'
8import { getSort } from '../utils'
9import { ActorModel } from './actor'
10
11@Table({
12 tableName: 'actorFollow',
13 indexes: [
14 {
15 fields: [ 'actorId' ]
16 },
17 {
18 fields: [ 'targetActorId' ]
19 },
20 {
21 fields: [ 'actorId', 'targetActorId' ],
22 unique: true
23 }
24 ]
25})
26export class ActorFollowModel extends Model<ActorFollowModel> {
27
28 @AllowNull(false)
29 @Column(DataType.ENUM(values(FOLLOW_STATES)))
30 state: FollowState
31
32 @CreatedAt
33 createdAt: Date
34
35 @UpdatedAt
36 updatedAt: Date
37
38 @ForeignKey(() => ActorModel)
39 @Column
40 actorId: number
41
42 @BelongsTo(() => ActorModel, {
43 foreignKey: {
44 name: 'actorId',
45 allowNull: false
46 },
47 as: 'ActorFollower',
48 onDelete: 'CASCADE'
49 })
50 ActorFollower: ActorModel
51
52 @ForeignKey(() => ActorModel)
53 @Column
54 targetActorId: number
55
56 @BelongsTo(() => ActorModel, {
57 foreignKey: {
58 name: 'targetActorId',
59 allowNull: false
60 },
61 as: 'ActorFollowing',
62 onDelete: 'CASCADE'
63 })
64 ActorFollowing: ActorModel
65
66 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) {
67 const query = {
68 where: {
69 actorId,
70 targetActorId: targetActorId
71 },
72 include: [
73 {
74 model: ActorModel,
75 required: true,
76 as: 'ActorFollower'
77 },
78 {
79 model: ActorModel,
80 required: true,
81 as: 'ActorFollowing'
82 }
83 ],
84 transaction: t
85 }
86
87 return ActorFollowModel.findOne(query)
88 }
89
90 static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) {
91 const query = {
92 where: {
93 actorId
94 },
95 include: [
96 {
97 model: ActorModel,
98 required: true,
99 as: 'ActorFollower'
100 },
101 {
102 model: ActorModel,
103 required: true,
104 as: 'ActorFollowing',
105 include: [
106 {
107 model: ServerModel,
108 required: true,
109 where: {
110 host: targetHost
111 }
112 }
113 ]
114 }
115 ],
116 transaction: t
117 }
118
119 return ActorFollowModel.findOne(query)
120 }
121
122 static listFollowingForApi (id: number, start: number, count: number, sort: string) {
123 const query = {
124 distinct: true,
125 offset: start,
126 limit: count,
127 order: [ getSort(sort) ],
128 include: [
129 {
130 model: ActorModel,
131 required: true,
132 as: 'ActorFollower',
133 where: {
134 id
135 }
136 },
137 {
138 model: ActorModel,
139 as: 'ActorFollowing',
140 required: true,
141 include: [ ServerModel ]
142 }
143 ]
144 }
145
146 return ActorFollowModel.findAndCountAll(query)
147 .then(({ rows, count }) => {
148 return {
149 data: rows,
150 total: count
151 }
152 })
153 }
154
155 static listFollowersForApi (id: number, start: number, count: number, sort: string) {
156 const query = {
157 distinct: true,
158 offset: start,
159 limit: count,
160 order: [ getSort(sort) ],
161 include: [
162 {
163 model: ActorModel,
164 required: true,
165 as: 'ActorFollower',
166 include: [ ServerModel ]
167 },
168 {
169 model: ActorModel,
170 as: 'ActorFollowing',
171 required: true,
172 where: {
173 id
174 }
175 }
176 ]
177 }
178
179 return ActorFollowModel.findAndCountAll(query)
180 .then(({ rows, count }) => {
181 return {
182 data: rows,
183 total: count
184 }
185 })
186 }
187
188 static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
189 return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count)
190 }
191
192 static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) {
193 return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, undefined, undefined, 'sharedInboxUrl')
194 }
195
196 static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
197 return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count)
198 }
199
200 private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
201 actorIds: number[],
202 t: Sequelize.Transaction,
203 start?: number,
204 count?: number,
205 columnUrl = 'url') {
206 let firstJoin: string
207 let secondJoin: string
208
209 if (type === 'followers') {
210 firstJoin = 'targetActorId'
211 secondJoin = 'actorId'
212 } else {
213 firstJoin = 'actorId'
214 secondJoin = 'targetActorId'
215 }
216
217 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
218 const tasks: Bluebird<any>[] = []
219
220 for (const selection of selections) {
221 let query = 'SELECT ' + selection + ' FROM "actor" ' +
222 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
223 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
224 'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' '
225
226 if (count !== undefined) query += 'LIMIT ' + count
227 if (start !== undefined) query += ' OFFSET ' + start
228
229 const options = {
230 bind: { actorIds },
231 type: Sequelize.QueryTypes.SELECT,
232 transaction: t
233 }
234 tasks.push(ActorFollowModel.sequelize.query(query, options))
235 }
236
237 const [ followers, [ { total } ] ] = await
238 Promise.all(tasks)
239 const urls: string[] = followers.map(f => f.url)
240
241 return {
242 data: urls,
243 total: parseInt(total, 10)
244 }
245 }
246
247 toFormattedJSON () {
248 const follower = this.ActorFollower.toFormattedJSON()
249 const following = this.ActorFollowing.toFormattedJSON()
250
251 return {
252 id: this.id,
253 follower,
254 following,
255 state: this.state,
256 createdAt: this.createdAt,
257 updatedAt: this.updatedAt
258 }
259 }
260}
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 4cae6a6ec..ecaa43dcf 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -1,38 +1,83 @@
1import { values } from 'lodash'
1import { join } from 'path' 2import { join } from 'path'
2import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
3import { 4import {
4 AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table, 5 AllowNull,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 DataType,
10 Default,
11 ForeignKey,
12 HasMany,
13 HasOne,
14 Is,
15 IsUUID,
16 Model,
17 Scopes,
18 Table,
5 UpdatedAt 19 UpdatedAt
6} from 'sequelize-typescript' 20} from 'sequelize-typescript'
21import { ActivityPubActorType } from '../../../shared/models/activitypub'
7import { Avatar } from '../../../shared/models/avatars/avatar.model' 22import { Avatar } from '../../../shared/models/avatars/avatar.model'
8import { activityPubContextify } from '../../helpers' 23import { activityPubContextify } from '../../helpers'
9import { 24import {
10 isActivityPubUrlValid, 25 isActivityPubUrlValid,
11 isActorFollowersCountValid, 26 isActorFollowersCountValid,
12 isActorFollowingCountValid, isActorPreferredUsernameValid, 27 isActorFollowingCountValid,
28 isActorNameValid,
13 isActorPrivateKeyValid, 29 isActorPrivateKeyValid,
14 isActorPublicKeyValid 30 isActorPublicKeyValid
15} from '../../helpers/custom-validators/activitypub' 31} from '../../helpers/custom-validators/activitypub'
16import { isUserUsernameValid } from '../../helpers/custom-validators/users' 32import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
17import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' 33import { AccountModel } from '../account/account'
18import { AccountFollowModel } from '../account/account-follow'
19import { AvatarModel } from '../avatar/avatar' 34import { AvatarModel } from '../avatar/avatar'
20import { ServerModel } from '../server/server' 35import { ServerModel } from '../server/server'
21import { throwIfNotValid } from '../utils' 36import { throwIfNotValid } from '../utils'
37import { VideoChannelModel } from '../video/video-channel'
38import { ActorFollowModel } from './actor-follow'
22 39
40enum ScopeNames {
41 FULL = 'FULL'
42}
43
44@Scopes({
45 [ScopeNames.FULL]: {
46 include: [
47 {
48 model: () => AccountModel,
49 required: false
50 },
51 {
52 model: () => VideoChannelModel,
53 required: false
54 }
55 ]
56 }
57})
23@Table({ 58@Table({
24 tableName: 'actor' 59 tableName: 'actor',
60 indexes: [
61 {
62 fields: [ 'name', 'serverId' ],
63 unique: true
64 }
65 ]
25}) 66})
26export class ActorModel extends Model<ActorModel> { 67export class ActorModel extends Model<ActorModel> {
27 68
28 @AllowNull(false) 69 @AllowNull(false)
70 @Column(DataType.ENUM(values(ACTIVITY_PUB_ACTOR_TYPES)))
71 type: ActivityPubActorType
72
73 @AllowNull(false)
29 @Default(DataType.UUIDV4) 74 @Default(DataType.UUIDV4)
30 @IsUUID(4) 75 @IsUUID(4)
31 @Column(DataType.UUID) 76 @Column(DataType.UUID)
32 uuid: string 77 uuid: string
33 78
34 @AllowNull(false) 79 @AllowNull(false)
35 @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name')) 80 @Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name'))
36 @Column 81 @Column
37 name: string 82 name: string
38 83
@@ -104,24 +149,24 @@ export class ActorModel extends Model<ActorModel> {
104 }) 149 })
105 Avatar: AvatarModel 150 Avatar: AvatarModel
106 151
107 @HasMany(() => AccountFollowModel, { 152 @HasMany(() => ActorFollowModel, {
108 foreignKey: { 153 foreignKey: {
109 name: 'accountId', 154 name: 'actorId',
110 allowNull: false 155 allowNull: false
111 }, 156 },
112 onDelete: 'cascade' 157 onDelete: 'cascade'
113 }) 158 })
114 AccountFollowing: AccountFollowModel[] 159 AccountFollowing: ActorFollowModel[]
115 160
116 @HasMany(() => AccountFollowModel, { 161 @HasMany(() => ActorFollowModel, {
117 foreignKey: { 162 foreignKey: {
118 name: 'targetAccountId', 163 name: 'targetActorId',
119 allowNull: false 164 allowNull: false
120 }, 165 },
121 as: 'followers', 166 as: 'followers',
122 onDelete: 'cascade' 167 onDelete: 'cascade'
123 }) 168 })
124 AccountFollowers: AccountFollowModel[] 169 AccountFollowers: ActorFollowModel[]
125 170
126 @ForeignKey(() => ServerModel) 171 @ForeignKey(() => ServerModel)
127 @Column 172 @Column
@@ -135,6 +180,36 @@ export class ActorModel extends Model<ActorModel> {
135 }) 180 })
136 Server: ServerModel 181 Server: ServerModel
137 182
183 @HasOne(() => AccountModel, {
184 foreignKey: {
185 allowNull: true
186 },
187 onDelete: 'cascade'
188 })
189 Account: AccountModel
190
191 @HasOne(() => VideoChannelModel, {
192 foreignKey: {
193 allowNull: true
194 },
195 onDelete: 'cascade'
196 })
197 VideoChannel: VideoChannelModel
198
199 static load (id: number) {
200 return ActorModel.scope(ScopeNames.FULL).findById(id)
201 }
202
203 static loadByUUID (uuid: string) {
204 const query = {
205 where: {
206 uuid
207 }
208 }
209
210 return ActorModel.scope(ScopeNames.FULL).findOne(query)
211 }
212
138 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { 213 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
139 const query = { 214 const query = {
140 where: { 215 where: {
@@ -145,7 +220,48 @@ export class ActorModel extends Model<ActorModel> {
145 transaction 220 transaction
146 } 221 }
147 222
148 return ActorModel.findAll(query) 223 return ActorModel.scope(ScopeNames.FULL).findAll(query)
224 }
225
226 static loadLocalByName (name: string) {
227 const query = {
228 where: {
229 name,
230 serverId: null
231 }
232 }
233
234 return ActorModel.scope(ScopeNames.FULL).findOne(query)
235 }
236
237 static loadByNameAndHost (name: string, host: string) {
238 const query = {
239 where: {
240 name
241 },
242 include: [
243 {
244 model: ServerModel,
245 required: true,
246 where: {
247 host
248 }
249 }
250 ]
251 }
252
253 return ActorModel.scope(ScopeNames.FULL).findOne(query)
254 }
255
256 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
257 const query = {
258 where: {
259 url
260 },
261 transaction
262 }
263
264 return ActorModel.scope(ScopeNames.FULL).findOne(query)
149 } 265 }
150 266
151 toFormattedJSON () { 267 toFormattedJSON () {
@@ -167,6 +283,7 @@ export class ActorModel extends Model<ActorModel> {
167 283
168 return { 284 return {
169 id: this.id, 285 id: this.id,
286 uuid: this.uuid,
170 host, 287 host,
171 score, 288 score,
172 followingCount: this.followingCount, 289 followingCount: this.followingCount,
@@ -175,28 +292,30 @@ export class ActorModel extends Model<ActorModel> {
175 } 292 }
176 } 293 }
177 294
178 toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') { 295 toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') {
179 let activityPubType 296 let activityPubType
180 if (type === 'Account') { 297 if (type === 'Account') {
181 activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' 298 activityPubType = 'Person' as 'Person'
299 } else if (type === 'Application') {
300 activityPubType = 'Application' as 'Application'
182 } else { // VideoChannel 301 } else { // VideoChannel
183 activityPubType = 'Group' 302 activityPubType = 'Group' as 'Group'
184 } 303 }
185 304
186 const json = { 305 const json = {
187 type, 306 type: activityPubType,
188 id: this.url, 307 id: this.url,
189 following: this.getFollowingUrl(), 308 following: this.getFollowingUrl(),
190 followers: this.getFollowersUrl(), 309 followers: this.getFollowersUrl(),
191 inbox: this.inboxUrl, 310 inbox: this.inboxUrl,
192 outbox: this.outboxUrl, 311 outbox: this.outboxUrl,
193 preferredUsername: name, 312 preferredUsername,
194 url: this.url, 313 url: this.url,
195 name, 314 name: this.name,
196 endpoints: { 315 endpoints: {
197 sharedInbox: this.sharedInboxUrl 316 sharedInbox: this.sharedInboxUrl
198 }, 317 },
199 uuid, 318 uuid: this.uuid,
200 publicKey: { 319 publicKey: {
201 id: this.getPublicKeyUrl(), 320 id: this.getPublicKeyUrl(),
202 owner: this.url, 321 owner: this.url,
@@ -212,11 +331,11 @@ export class ActorModel extends Model<ActorModel> {
212 attributes: [ 'sharedInboxUrl' ], 331 attributes: [ 'sharedInboxUrl' ],
213 include: [ 332 include: [
214 { 333 {
215 model: AccountFollowModel, 334 model: ActorFollowModel,
216 required: true, 335 required: true,
217 as: 'followers', 336 as: 'followers',
218 where: { 337 where: {
219 targetAccountId: this.id 338 targetActorId: this.id
220 } 339 }
221 } 340 }
222 ], 341 ],
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
index 9fc07e850..854a5fb36 100644
--- a/server/models/application/application.ts
+++ b/server/models/application/application.ts
@@ -1,5 +1,14 @@
1import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' 1import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
2import { AccountModel } from '../account/account'
2 3
4@DefaultScope({
5 include: [
6 {
7 model: () => AccountModel,
8 required: true
9 }
10 ]
11})
3@Table({ 12@Table({
4 tableName: 'application' 13 tableName: 'application'
5}) 14})
@@ -11,7 +20,19 @@ export class ApplicationModel extends Model<ApplicationModel> {
11 @Column 20 @Column
12 migrationVersion: number 21 migrationVersion: number
13 22
23 @HasOne(() => AccountModel, {
24 foreignKey: {
25 allowNull: true
26 },
27 onDelete: 'cascade'
28 })
29 Account: AccountModel
30
14 static countTotal () { 31 static countTotal () {
15 return ApplicationModel.count() 32 return ApplicationModel.count()
16 } 33 }
34
35 static load () {
36 return ApplicationModel.findOne()
37 }
17} 38}
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index d0ee969fb..182971c4e 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -3,7 +3,6 @@ import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
3import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' 3import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos'
4import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers'
5import { AccountModel } from '../account/account' 5import { AccountModel } from '../account/account'
6import { ServerModel } from '../server/server'
7import { getSort, throwIfNotValid } from '../utils' 6import { getSort, throwIfNotValid } from '../utils'
8import { VideoModel } from './video' 7import { VideoModel } from './video'
9 8
@@ -63,13 +62,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
63 include: [ 62 include: [
64 { 63 {
65 model: AccountModel, 64 model: AccountModel,
66 required: true, 65 required: true
67 include: [
68 {
69 model: ServerModel,
70 required: false
71 }
72 ]
73 }, 66 },
74 { 67 {
75 model: VideoModel, 68 model: VideoModel,
@@ -87,8 +80,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
87 toFormattedJSON () { 80 toFormattedJSON () {
88 let reporterServerHost 81 let reporterServerHost
89 82
90 if (this.Account.Server) { 83 if (this.Account.Actor.Server) {
91 reporterServerHost = this.Account.Server.host 84 reporterServerHost = this.Account.Actor.Server.host
92 } else { 85 } else {
93 // It means it's our video 86 // It means it's our video
94 reporterServerHost = CONFIG.WEBSERVER.HOST 87 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
index f5b7a7cd5..000000000
--- a/server/models/video/video-channel-share.ts
+++ /dev/null
@@ -1,96 +0,0 @@
1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account'
4import { VideoChannelModel } from './video-channel'
5
6enum ScopeNames {
7 FULL = 'FULL',
8 WITH_ACCOUNT = 'WITH_ACCOUNT'
9}
10
11@Scopes({
12 [ScopeNames.FULL]: {
13 include: [
14 {
15 model: () => AccountModel,
16 required: true
17 },
18 {
19 model: () => VideoChannelModel,
20 required: true
21 }
22 ]
23 },
24 [ScopeNames.WITH_ACCOUNT]: {
25 include: [
26 {
27 model: () => AccountModel,
28 required: true
29 }
30 ]
31 }
32})
33@Table({
34 tableName: 'videoChannelShare',
35 indexes: [
36 {
37 fields: [ 'accountId' ]
38 },
39 {
40 fields: [ 'videoChannelId' ]
41 }
42 ]
43})
44export class VideoChannelShareModel extends Model<VideoChannelShareModel> {
45 @CreatedAt
46 createdAt: Date
47
48 @UpdatedAt
49 updatedAt: Date
50
51 @ForeignKey(() => AccountModel)
52 @Column
53 accountId: number
54
55 @BelongsTo(() => AccountModel, {
56 foreignKey: {
57 allowNull: false
58 },
59 onDelete: 'cascade'
60 })
61 Account: AccountModel
62
63 @ForeignKey(() => VideoChannelModel)
64 @Column
65 videoChannelId: number
66
67 @BelongsTo(() => VideoChannelModel, {
68 foreignKey: {
69 allowNull: false
70 },
71 onDelete: 'cascade'
72 })
73 VideoChannel: VideoChannelModel
74
75 static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
76 return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({
77 where: {
78 accountId,
79 videoChannelId
80 },
81 transaction: t
82 })
83 }
84
85 static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) {
86 const query = {
87 where: {
88 videoChannelId
89 },
90 transaction: t
91 }
92
93 return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query)
94 .then(res => res.map(r => r.Account))
95 }
96}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index fe44d3d53..acc2486b3 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -1,42 +1,52 @@
1import * as Sequelize from 'sequelize'
2import { 1import {
3 AfterDestroy, 2 AfterDestroy,
4 AllowNull, 3 AllowNull,
5 BelongsTo, 4 BelongsTo,
6 Column, 5 Column,
7 CreatedAt, 6 CreatedAt,
8 DataType, 7 DefaultScope,
9 Default,
10 ForeignKey, 8 ForeignKey,
11 HasMany, 9 HasMany,
12 Is, 10 Is,
13 IsUUID,
14 Model, 11 Model,
15 Scopes, 12 Scopes,
16 Table, 13 Table,
17 UpdatedAt 14 UpdatedAt
18} from 'sequelize-typescript' 15} from 'sequelize-typescript'
19import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' 16import { ActivityPubActor } from '../../../shared/models/activitypub'
20import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' 17import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
21import { sendDeleteVideoChannel } from '../../lib/activitypub/send' 18import { sendDeleteActor } from '../../lib/activitypub/send'
22import { AccountModel } from '../account/account' 19import { AccountModel } from '../account/account'
23import { ActorModel } from '../activitypub/actor' 20import { ActorModel } from '../activitypub/actor'
24import { ServerModel } from '../server/server'
25import { getSort, throwIfNotValid } from '../utils' 21import { getSort, throwIfNotValid } from '../utils'
26import { VideoModel } from './video' 22import { VideoModel } from './video'
27import { VideoChannelShareModel } from './video-channel-share'
28 23
29enum ScopeNames { 24enum ScopeNames {
30 WITH_ACCOUNT = 'WITH_ACCOUNT', 25 WITH_ACCOUNT = 'WITH_ACCOUNT',
26 WITH_ACTOR = 'WITH_ACTOR',
31 WITH_VIDEOS = 'WITH_VIDEOS' 27 WITH_VIDEOS = 'WITH_VIDEOS'
32} 28}
33 29
30@DefaultScope({
31 include: [
32 {
33 model: () => ActorModel,
34 required: true
35 }
36 ]
37})
34@Scopes({ 38@Scopes({
35 [ScopeNames.WITH_ACCOUNT]: { 39 [ScopeNames.WITH_ACCOUNT]: {
36 include: [ 40 include: [
37 { 41 {
38 model: () => AccountModel, 42 model: () => AccountModel,
39 include: [ { model: () => ServerModel, required: false } ] 43 required: true,
44 include: [
45 {
46 model: () => ActorModel,
47 required: true
48 }
49 ]
40 } 50 }
41 ] 51 ]
42 }, 52 },
@@ -44,6 +54,11 @@ enum ScopeNames {
44 include: [ 54 include: [
45 () => VideoModel 55 () => VideoModel
46 ] 56 ]
57 },
58 [ScopeNames.WITH_ACTOR]: {
59 include: [
60 () => ActorModel
61 ]
47 } 62 }
48}) 63})
49@Table({ 64@Table({
@@ -57,12 +72,6 @@ enum ScopeNames {
57export class VideoChannelModel extends Model<VideoChannelModel> { 72export class VideoChannelModel extends Model<VideoChannelModel> {
58 73
59 @AllowNull(false) 74 @AllowNull(false)
60 @Default(DataType.UUIDV4)
61 @IsUUID(4)
62 @Column(DataType.UUID)
63 uuid: string
64
65 @AllowNull(false)
66 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) 75 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
67 @Column 76 @Column
68 name: string 77 name: string
@@ -72,10 +81,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
72 @Column 81 @Column
73 description: string 82 description: string
74 83
75 @AllowNull(false)
76 @Column
77 remote: boolean
78
79 @CreatedAt 84 @CreatedAt
80 createdAt: Date 85 createdAt: Date
81 86
@@ -115,19 +120,10 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
115 }) 120 })
116 Videos: VideoModel[] 121 Videos: VideoModel[]
117 122
118 @HasMany(() => VideoChannelShareModel, {
119 foreignKey: {
120 name: 'channelId',
121 allowNull: false
122 },
123 onDelete: 'CASCADE'
124 })
125 VideoChannelShares: VideoChannelShareModel[]
126
127 @AfterDestroy 123 @AfterDestroy
128 static sendDeleteIfOwned (instance: VideoChannelModel) { 124 static sendDeleteIfOwned (instance: VideoChannelModel) {
129 if (instance.isOwned()) { 125 if (instance.Actor.isOwned()) {
130 return sendDeleteVideoChannel(instance, undefined) 126 return sendDeleteActor(instance.Actor, undefined)
131 } 127 }
132 128
133 return undefined 129 return undefined
@@ -150,7 +146,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
150 order: [ getSort(sort) ] 146 order: [ getSort(sort) ]
151 } 147 }
152 148
153 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query) 149 return VideoChannelModel
150 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
151 .findAndCountAll(query)
154 .then(({ rows, count }) => { 152 .then(({ rows, count }) => {
155 return { total: count, data: rows } 153 return { total: count, data: rows }
156 }) 154 })
@@ -165,51 +163,18 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
165 where: { 163 where: {
166 id: accountId 164 id: accountId
167 }, 165 },
168 required: true, 166 required: true
169 include: [ { model: ServerModel, required: false } ]
170 } 167 }
171 ] 168 ]
172 } 169 }
173 170
174 return VideoChannelModel.findAndCountAll(query) 171 return VideoChannelModel
172 .findAndCountAll(query)
175 .then(({ rows, count }) => { 173 .then(({ rows, count }) => {
176 return { total: count, data: rows } 174 return { total: count, data: rows }
177 }) 175 })
178 } 176 }
179 177
180 static loadByUrl (url: string, t?: Sequelize.Transaction) {
181 const query: IFindOptions<VideoChannelModel> = {
182 include: [
183 {
184 model: ActorModel,
185 required: true,
186 where: {
187 url
188 }
189 }
190 ]
191 }
192
193 if (t !== undefined) query.transaction = t
194
195 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
196 }
197
198 static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) {
199 const query: IFindOptions<VideoChannelModel> = {
200 where: {
201 [ Sequelize.Op.or ]: [
202 { uuid },
203 { url }
204 ]
205 }
206 }
207
208 if (t !== undefined) query.transaction = t
209
210 return VideoChannelModel.findOne(query)
211 }
212
213 static loadByIdAndAccount (id: number, accountId: number) { 178 static loadByIdAndAccount (id: number, accountId: number) {
214 const options = { 179 const options = {
215 where: { 180 where: {
@@ -218,21 +183,33 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
218 } 183 }
219 } 184 }
220 185
221 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) 186 return VideoChannelModel
187 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
188 .findOne(options)
222 } 189 }
223 190
224 static loadAndPopulateAccount (id: number) { 191 static loadAndPopulateAccount (id: number) {
225 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id) 192 return VideoChannelModel
193 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
194 .findById(id)
226 } 195 }
227 196
228 static loadByUUIDAndPopulateAccount (uuid: string) { 197 static loadByUUIDAndPopulateAccount (uuid: string) {
229 const options = { 198 const options = {
230 where: { 199 include: [
231 uuid 200 {
232 } 201 model: ActorModel,
202 required: true,
203 where: {
204 uuid
205 }
206 }
207 ]
233 } 208 }
234 209
235 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) 210 return VideoChannelModel
211 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
212 .findOne(options)
236 } 213 }
237 214
238 static loadAndPopulateAccountAndVideos (id: number) { 215 static loadAndPopulateAccountAndVideos (id: number) {
@@ -242,39 +219,36 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
242 ] 219 ]
243 } 220 }
244 221
245 return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options) 222 return VideoChannelModel
246 } 223 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
247 224 .findById(id, options)
248 isOwned () {
249 return this.remote === false
250 } 225 }
251 226
252 toFormattedJSON () { 227 toFormattedJSON () {
253 const json = { 228 const actor = this.Actor.toFormattedJSON()
229 const account = {
254 id: this.id, 230 id: this.id,
255 uuid: this.uuid,
256 name: this.name, 231 name: this.name,
257 description: this.description, 232 description: this.description,
258 isLocal: this.isOwned(), 233 isLocal: this.Actor.isOwned(),
259 createdAt: this.createdAt, 234 createdAt: this.createdAt,
260 updatedAt: this.updatedAt 235 updatedAt: this.updatedAt
261 } 236 }
262 237
263 if (this.Account !== undefined) { 238 return Object.assign(actor, account)
264 json[ 'owner' ] = {
265 name: this.Account.name,
266 uuid: this.Account.uuid
267 }
268 }
269
270 if (Array.isArray(this.Videos)) {
271 json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON())
272 }
273
274 return json
275 } 239 }
276 240
277 toActivityPubObject () { 241 toActivityPubObject (): ActivityPubActor {
278 return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel') 242 const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
243
244 return Object.assign(obj, {
245 summary: this.description,
246 attributedTo: [
247 {
248 type: 'Person' as 'Person',
249 id: this.Account.Actor.url
250 }
251 ]
252 })
279 } 253 }
280} 254}
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index e1733b3a7..c252fd646 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,18 +1,18 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account' 3import { ActorModel } from '../activitypub/actor'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5 5
6enum ScopeNames { 6enum ScopeNames {
7 FULL = 'FULL', 7 FULL = 'FULL',
8 WITH_ACCOUNT = 'WITH_ACCOUNT' 8 WITH_ACTOR = 'WITH_ACTOR'
9} 9}
10 10
11@Scopes({ 11@Scopes({
12 [ScopeNames.FULL]: { 12 [ScopeNames.FULL]: {
13 include: [ 13 include: [
14 { 14 {
15 model: () => AccountModel, 15 model: () => ActorModel,
16 required: true 16 required: true
17 }, 17 },
18 { 18 {
@@ -21,10 +21,10 @@ enum ScopeNames {
21 } 21 }
22 ] 22 ]
23 }, 23 },
24 [ScopeNames.WITH_ACCOUNT]: { 24 [ScopeNames.WITH_ACTOR]: {
25 include: [ 25 include: [
26 { 26 {
27 model: () => AccountModel, 27 model: () => ActorModel,
28 required: true 28 required: true
29 } 29 }
30 ] 30 ]
@@ -34,7 +34,7 @@ enum ScopeNames {
34 tableName: 'videoShare', 34 tableName: 'videoShare',
35 indexes: [ 35 indexes: [
36 { 36 {
37 fields: [ 'accountId' ] 37 fields: [ 'actorId' ]
38 }, 38 },
39 { 39 {
40 fields: [ 'videoId' ] 40 fields: [ 'videoId' ]
@@ -48,17 +48,17 @@ export class VideoShareModel extends Model<VideoShareModel> {
48 @UpdatedAt 48 @UpdatedAt
49 updatedAt: Date 49 updatedAt: Date
50 50
51 @ForeignKey(() => AccountModel) 51 @ForeignKey(() => ActorModel)
52 @Column 52 @Column
53 accountId: number 53 actorId: number
54 54
55 @BelongsTo(() => AccountModel, { 55 @BelongsTo(() => ActorModel, {
56 foreignKey: { 56 foreignKey: {
57 allowNull: false 57 allowNull: false
58 }, 58 },
59 onDelete: 'cascade' 59 onDelete: 'cascade'
60 }) 60 })
61 Account: AccountModel 61 Actor: ActorModel
62 62
63 @ForeignKey(() => VideoModel) 63 @ForeignKey(() => VideoModel)
64 @Column 64 @Column
@@ -72,24 +72,24 @@ export class VideoShareModel extends Model<VideoShareModel> {
72 }) 72 })
73 Video: VideoModel 73 Video: VideoModel
74 74
75 static load (accountId: number, videoId: number, t: Sequelize.Transaction) { 75 static load (actorId: number, videoId: number, t: Sequelize.Transaction) {
76 return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({ 76 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
77 where: { 77 where: {
78 accountId, 78 actorId,
79 videoId 79 videoId
80 }, 80 },
81 transaction: t 81 transaction: t
82 }) 82 })
83 } 83 }
84 84
85 static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) { 85 static loadActorsByShare (videoId: number, t: Sequelize.Transaction) {
86 const query = { 86 const query = {
87 where: { 87 where: {
88 videoId 88 videoId
89 }, 89 },
90 include: [ 90 include: [
91 { 91 {
92 model: AccountModel, 92 model: ActorModel,
93 required: true 93 required: true
94 } 94 }
95 ], 95 ],
@@ -97,6 +97,6 @@ export class VideoShareModel extends Model<VideoShareModel> {
97 } 97 }
98 98
99 return VideoShareModel.scope(ScopeNames.FULL).findAll(query) 99 return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
100 .then(res => res.map(r => r.Account)) 100 .then(res => res.map(r => r.Actor))
101 } 101 }
102} 102}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 1f940a50d..97fdbc8ef 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -66,9 +66,10 @@ import {
66 VIDEO_PRIVACIES 66 VIDEO_PRIVACIES
67} from '../../initializers' 67} from '../../initializers'
68import { getAnnounceActivityPubUrl } from '../../lib/activitypub' 68import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
69import { sendDeleteVideo } from '../../lib/index' 69import { sendDeleteVideo } from '../../lib/activitypub/send'
70import { AccountModel } from '../account/account' 70import { AccountModel } from '../account/account'
71import { AccountVideoRateModel } from '../account/account-video-rate' 71import { AccountVideoRateModel } from '../account/account-video-rate'
72import { ActorModel } from '../activitypub/actor'
72import { ServerModel } from '../server/server' 73import { ServerModel } from '../server/server'
73import { getSort, throwIfNotValid } from '../utils' 74import { getSort, throwIfNotValid } from '../utils'
74import { TagModel } from './tag' 75import { TagModel } from './tag'
@@ -79,8 +80,7 @@ import { VideoShareModel } from './video-share'
79import { VideoTagModel } from './video-tag' 80import { VideoTagModel } from './video-tag'
80 81
81enum ScopeNames { 82enum ScopeNames {
82 NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST', 83 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
83 PUBLIC = 'PUBLIC',
84 WITH_ACCOUNT = 'WITH_ACCOUNT', 84 WITH_ACCOUNT = 'WITH_ACCOUNT',
85 WITH_TAGS = 'WITH_TAGS', 85 WITH_TAGS = 'WITH_TAGS',
86 WITH_FILES = 'WITH_FILES', 86 WITH_FILES = 'WITH_FILES',
@@ -89,17 +89,13 @@ enum ScopeNames {
89} 89}
90 90
91@Scopes({ 91@Scopes({
92 [ScopeNames.NOT_IN_BLACKLIST]: { 92 [ScopeNames.AVAILABLE_FOR_LIST]: {
93 where: { 93 where: {
94 id: { 94 id: {
95 [Sequelize.Op.notIn]: Sequelize.literal( 95 [Sequelize.Op.notIn]: Sequelize.literal(
96 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 96 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
97 ) 97 )
98 } 98 },
99 }
100 },
101 [ScopeNames.PUBLIC]: {
102 where: {
103 privacy: VideoPrivacy.PUBLIC 99 privacy: VideoPrivacy.PUBLIC
104 } 100 }
105 }, 101 },
@@ -114,8 +110,14 @@ enum ScopeNames {
114 required: true, 110 required: true,
115 include: [ 111 include: [
116 { 112 {
117 model: () => ServerModel, 113 model: () => ActorModel,
118 required: false 114 required: true,
115 include: [
116 {
117 model: () => ServerModel,
118 required: false
119 }
120 ]
119 } 121 }
120 ] 122 ]
121 } 123 }
@@ -138,7 +140,7 @@ enum ScopeNames {
138 include: [ 140 include: [
139 { 141 {
140 model: () => VideoShareModel, 142 model: () => VideoShareModel,
141 include: [ () => AccountModel ] 143 include: [ () => ActorModel ]
142 } 144 }
143 ] 145 ]
144 }, 146 },
@@ -271,7 +273,7 @@ export class VideoModel extends Model<VideoModel> {
271 273
272 @BelongsTo(() => VideoChannelModel, { 274 @BelongsTo(() => VideoChannelModel, {
273 foreignKey: { 275 foreignKey: {
274 allowNull: false 276 allowNull: true
275 }, 277 },
276 onDelete: 'cascade' 278 onDelete: 'cascade'
277 }) 279 })
@@ -351,14 +353,15 @@ export class VideoModel extends Model<VideoModel> {
351 return VideoModel.scope(ScopeNames.WITH_FILES).findAll() 353 return VideoModel.scope(ScopeNames.WITH_FILES).findAll()
352 } 354 }
353 355
354 static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { 356 static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) {
355 function getRawQuery (select: string) { 357 function getRawQuery (select: string) {
356 const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + 358 const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' +
357 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + 359 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
358 'WHERE "VideoChannel"."accountId" = ' + accountId 360 'INNER JOIN "account" AS "Account" ON "Account"."id" = "VideoChannel"."accountId" ' +
361 'WHERE "Account"."actorId" = ' + actorId
359 const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' + 362 const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' +
360 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + 363 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
361 'WHERE "VideoShare"."accountId" = ' + accountId 364 'WHERE "VideoShare"."actorId" = ' + actorId
362 365
363 return `(${queryVideo}) UNION (${queryVideoShare})` 366 return `(${queryVideo}) UNION (${queryVideoShare})`
364 } 367 }
@@ -388,11 +391,16 @@ export class VideoModel extends Model<VideoModel> {
388 } 391 }
389 }, 392 },
390 { 393 {
391 accountId 394 actorId
392 } 395 }
393 ] 396 ]
394 }, 397 },
395 include: [ AccountModel ] 398 include: [
399 {
400 model: ActorModel,
401 required: true
402 }
403 ]
396 }, 404 },
397 { 405 {
398 model: VideoChannelModel, 406 model: VideoChannelModel,
@@ -469,7 +477,7 @@ export class VideoModel extends Model<VideoModel> {
469 order: [ getSort(sort) ] 477 order: [ getSort(sort) ]
470 } 478 }
471 479
472 return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ]) 480 return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST, ScopeNames.WITH_ACCOUNT ])
473 .findAndCountAll(query) 481 .findAndCountAll(query)
474 .then(({ rows, count }) => { 482 .then(({ rows, count }) => {
475 return { 483 return {
@@ -541,7 +549,13 @@ export class VideoModel extends Model<VideoModel> {
541 549
542 const accountInclude: IIncludeOptions = { 550 const accountInclude: IIncludeOptions = {
543 model: AccountModel, 551 model: AccountModel,
544 include: [ serverInclude ] 552 include: [
553 {
554 model: ActorModel,
555 required: true,
556 include: [ serverInclude ]
557 }
558 ]
545 } 559 }
546 560
547 const videoChannelInclude: IIncludeOptions = { 561 const videoChannelInclude: IIncludeOptions = {
@@ -586,7 +600,7 @@ export class VideoModel extends Model<VideoModel> {
586 videoChannelInclude, tagInclude 600 videoChannelInclude, tagInclude
587 ] 601 ]
588 602
589 return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ]) 603 return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST ])
590 .findAndCountAll(query).then(({ rows, count }) => { 604 .findAndCountAll(query).then(({ rows, count }) => {
591 return { 605 return {
592 data: rows, 606 data: rows,
@@ -688,8 +702,8 @@ export class VideoModel extends Model<VideoModel> {
688 toFormattedJSON () { 702 toFormattedJSON () {
689 let serverHost 703 let serverHost
690 704
691 if (this.VideoChannel.Account.Server) { 705 if (this.VideoChannel.Account.Actor.Server) {
692 serverHost = this.VideoChannel.Account.Server.host 706 serverHost = this.VideoChannel.Account.Actor.Server.host
693 } else { 707 } else {
694 // It means it's our video 708 // It means it's our video
695 serverHost = CONFIG.WEBSERVER.HOST 709 serverHost = CONFIG.WEBSERVER.HOST
@@ -805,9 +819,9 @@ export class VideoModel extends Model<VideoModel> {
805 819
806 for (const rate of this.AccountVideoRates) { 820 for (const rate of this.AccountVideoRates) {
807 if (rate.type === 'like') { 821 if (rate.type === 'like') {
808 likes.push(rate.Account.url) 822 likes.push(rate.Account.Actor.url)
809 } else if (rate.type === 'dislike') { 823 } else if (rate.type === 'dislike') {
810 dislikes.push(rate.Account.url) 824 dislikes.push(rate.Account.Actor.url)
811 } 825 }
812 } 826 }
813 827
@@ -820,7 +834,7 @@ export class VideoModel extends Model<VideoModel> {
820 const shares: string[] = [] 834 const shares: string[] = []
821 835
822 for (const videoShare of this.VideoShares) { 836 for (const videoShare of this.VideoShares) {
823 const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) 837 const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor)
824 shares.push(shareUrl) 838 shares.push(shareUrl)
825 } 839 }
826 840
@@ -886,7 +900,13 @@ export class VideoModel extends Model<VideoModel> {
886 url, 900 url,
887 likes: likesObject, 901 likes: likesObject,
888 dislikes: dislikesObject, 902 dislikes: dislikesObject,
889 shares: sharesObject 903 shares: sharesObject,
904 attributedTo: [
905 {
906 type: 'Group',
907 id: this.VideoChannel.Actor.url
908 }
909 ]
890 } 910 }
891 } 911 }
892 912
@@ -1030,8 +1050,8 @@ export class VideoModel extends Model<VideoModel> {
1030 baseUrlHttp = CONFIG.WEBSERVER.URL 1050 baseUrlHttp = CONFIG.WEBSERVER.URL
1031 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 1051 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
1032 } else { 1052 } else {
1033 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host 1053 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host
1034 baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host 1054 baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host
1035 } 1055 }
1036 1056
1037 return { baseUrlHttp, baseUrlWs } 1057 return { baseUrlHttp, baseUrlWs }
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts
index 0af1562f5..cdd2783df 100644
--- a/server/tests/api/check-params/follows.ts
+++ b/server/tests/api/check-params/follows.ts
@@ -184,25 +184,9 @@ describe('Test server follows API validators', function () {
184 .expect(403) 184 .expect(403)
185 }) 185 })
186 186
187 it('Should fail with an undefined id', async function () {
188 await request(server.url)
189 .delete(path + '/' + undefined)
190 .set('Authorization', 'Bearer ' + server.accessToken)
191 .set('Accept', 'application/json')
192 .expect(400)
193 })
194
195 it('Should fail with an invalid id', async function () {
196 await request(server.url)
197 .delete(path + '/foobar')
198 .set('Authorization', 'Bearer ' + server.accessToken)
199 .set('Accept', 'application/json')
200 .expect(400)
201 })
202
203 it('Should fail we do not follow this server', async function () { 187 it('Should fail we do not follow this server', async function () {
204 await request(server.url) 188 await request(server.url)
205 .delete(path + '/-1') 189 .delete(path + '/example.com')
206 .set('Authorization', 'Bearer ' + server.accessToken) 190 .set('Authorization', 'Bearer ' + server.accessToken)
207 .set('Accept', 'application/json') 191 .set('Accept', 'application/json')
208 .expect(404) 192 .expect(404)
diff --git a/server/tests/api/follows.ts b/server/tests/api/follows.ts
index dcb4c8bd9..10eb48969 100644
--- a/server/tests/api/follows.ts
+++ b/server/tests/api/follows.ts
@@ -23,7 +23,6 @@ const expect = chai.expect
23 23
24describe('Test follows', function () { 24describe('Test follows', function () {
25 let servers: ServerInfo[] = [] 25 let servers: ServerInfo[] = []
26 let server3Id: number
27 26
28 before(async function () { 27 before(async function () {
29 this.timeout(20000) 28 this.timeout(20000)
@@ -82,8 +81,6 @@ describe('Test follows', function () {
82 expect(server3Follow).to.not.be.undefined 81 expect(server3Follow).to.not.be.undefined
83 expect(server2Follow.state).to.equal('accepted') 82 expect(server2Follow.state).to.equal('accepted')
84 expect(server3Follow.state).to.equal('accepted') 83 expect(server3Follow.state).to.equal('accepted')
85
86 server3Id = server3Follow.following.id
87 }) 84 })
88 85
89 it('Should have 0 followings on server 1 and 2', async function () { 86 it('Should have 0 followings on server 1 and 2', async function () {
@@ -121,7 +118,7 @@ describe('Test follows', function () {
121 it('Should unfollow server 3 on server 1', async function () { 118 it('Should unfollow server 3 on server 1', async function () {
122 this.timeout(5000) 119 this.timeout(5000)
123 120
124 await unfollow(servers[0].url, servers[0].accessToken, server3Id) 121 await unfollow(servers[0].url, servers[0].accessToken, servers[2])
125 122
126 await wait(3000) 123 await wait(3000)
127 }) 124 })
diff --git a/server/tests/utils/follows.ts b/server/tests/utils/follows.ts
index 033c6a719..a9f798bcb 100644
--- a/server/tests/utils/follows.ts
+++ b/server/tests/utils/follows.ts
@@ -42,8 +42,8 @@ async function follow (follower: string, following: string[], accessToken: strin
42 return res 42 return res
43} 43}
44 44
45async function unfollow (url: string, accessToken: string, id: number, expectedStatus = 204) { 45async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
46 const path = '/api/v1/server/following/' + id 46 const path = '/api/v1/server/following/' + target.host
47 47
48 const res = await request(url) 48 const res = await request(url)
49 .delete(path) 49 .delete(path)
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index 37f5400b9..1d248d3d7 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -1,14 +1,14 @@
1import { ActivityPubSignature } from './activitypub-signature' 1import { ActivityPubSignature } from './activitypub-signature'
2import { VideoChannelObject, VideoTorrentObject } from './objects' 2import { VideoTorrentObject } from './objects'
3import { DislikeObject } from './objects/dislike-object' 3import { DislikeObject } from './objects/dislike-object'
4import { VideoAbuseObject } from './objects/video-abuse-object' 4import { VideoAbuseObject } from './objects/video-abuse-object'
5import { ViewObject } from './objects/view-object' 5import { ViewObject } from './objects/view-object'
6 6
7export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | 7export type Activity = ActivityCreate | ActivityUpdate |
8 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | 8 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
9 ActivityUndo | ActivityLike 9 ActivityUndo | ActivityLike
10 10
11export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like' 11export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like'
12 12
13export interface ActivityAudience { 13export interface ActivityAudience {
14 to: string[] 14 to: string[]
@@ -27,18 +27,12 @@ export interface BaseActivity {
27 27
28export interface ActivityCreate extends BaseActivity { 28export interface ActivityCreate extends BaseActivity {
29 type: 'Create' 29 type: 'Create'
30 object: VideoChannelObject | VideoAbuseObject | ViewObject | DislikeObject 30 object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject
31}
32
33export interface ActivityAdd extends BaseActivity {
34 type: 'Add'
35 target: string
36 object: VideoTorrentObject
37} 31}
38 32
39export interface ActivityUpdate extends BaseActivity { 33export interface ActivityUpdate extends BaseActivity {
40 type: 'Update' 34 type: 'Update'
41 object: VideoTorrentObject | VideoChannelObject 35 object: VideoTorrentObject
42} 36}
43 37
44export interface ActivityDelete extends BaseActivity { 38export interface ActivityDelete extends BaseActivity {
@@ -56,7 +50,7 @@ export interface ActivityAccept extends BaseActivity {
56 50
57export interface ActivityAnnounce extends BaseActivity { 51export interface ActivityAnnounce extends BaseActivity {
58 type: 'Announce' 52 type: 'Announce'
59 object: ActivityCreate | ActivityAdd 53 object: ActivityCreate
60} 54}
61 55
62export interface ActivityUndo extends BaseActivity { 56export interface ActivityUndo extends BaseActivity {
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index 05b911d81..d9f80b94c 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -1,6 +1,10 @@
1import { ActivityPubAttributedTo } from './objects/common-objects'
2
3export type ActivityPubActorType = 'Person' | 'Application' | 'Group'
4
1export interface ActivityPubActor { 5export interface ActivityPubActor {
2 '@context': any[] 6 '@context': any[]
3 type: 'Person' | 'Application' | 'Group' 7 type: ActivityPubActorType
4 id: string 8 id: string
5 following: string 9 following: string
6 followers: string 10 followers: string
@@ -12,6 +16,8 @@ export interface ActivityPubActor {
12 endpoints: { 16 endpoints: {
13 sharedInbox: string 17 sharedInbox: string
14 } 18 }
19 summary: string
20 attributedTo: ActivityPubAttributedTo[]
15 21
16 uuid: string 22 uuid: string
17 publicKey: { 23 publicKey: {
@@ -21,7 +27,6 @@ export interface ActivityPubActor {
21 } 27 }
22 28
23 // Not used 29 // Not used
24 // summary: string
25 // icon: string[] 30 // icon: string[]
26 // liked: string 31 // liked: string
27} 32}
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index 3eaab21b5..ea5a503ac 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -23,3 +23,8 @@ export interface ActivityUrlObject {
23 width: number 23 width: number
24 size?: number 24 size?: number
25} 25}
26
27export interface ActivityPubAttributedTo {
28 type: 'Group' | 'Person'
29 id: string
30}
diff --git a/shared/models/activitypub/objects/index.ts b/shared/models/activitypub/objects/index.ts
index f1f761e44..3efd3ef13 100644
--- a/shared/models/activitypub/objects/index.ts
+++ b/shared/models/activitypub/objects/index.ts
@@ -1,6 +1,5 @@
1export * from './common-objects' 1export * from './common-objects'
2export * from './video-abuse-object' 2export * from './video-abuse-object'
3export * from './video-channel-object'
4export * from './video-torrent-object' 3export * from './video-torrent-object'
5export * from './view-object' 4export * from './view-object'
6export * from './dislike-object' 5export * 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
index dcce8696b..000000000
--- a/shared/models/activitypub/objects/video-channel-object.ts
+++ /dev/null
@@ -1,13 +0,0 @@
1import { ActivityPubOrderedCollection } from '../activitypub-ordered-collection'
2
3export interface VideoChannelObject {
4 type: 'VideoChannel'
5 id: string
6 name: string
7 content: string
8 uuid: string
9 published: string
10 updated: string
11 actor?: string
12 shares?: ActivityPubOrderedCollection<string>
13}
diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts
index a15ec7142..1405f7748 100644
--- a/shared/models/activitypub/objects/video-torrent-object.ts
+++ b/shared/models/activitypub/objects/video-torrent-object.ts
@@ -1,6 +1,6 @@
1import { 1import {
2 ActivityIconObject, 2 ActivityIconObject,
3 ActivityIdentifierObject, 3 ActivityIdentifierObject, ActivityPubAttributedTo,
4 ActivityTagObject, 4 ActivityTagObject,
5 ActivityUrlObject 5 ActivityUrlObject
6} from './common-objects' 6} from './common-objects'
@@ -24,8 +24,8 @@ export interface VideoTorrentObject {
24 content: string 24 content: string
25 icon: ActivityIconObject 25 icon: ActivityIconObject
26 url: ActivityUrlObject[] 26 url: ActivityUrlObject[]
27 actor?: string
28 likes?: ActivityPubOrderedCollection<string> 27 likes?: ActivityPubOrderedCollection<string>
29 dislikes?: ActivityPubOrderedCollection<string> 28 dislikes?: ActivityPubOrderedCollection<string>
30 shares?: ActivityPubOrderedCollection<string> 29 shares?: ActivityPubOrderedCollection<string>
30 attributedTo: ActivityPubAttributedTo[]
31} 31}
diff --git a/shared/models/accounts/account.model.ts b/shared/models/actors/account.model.ts
index d14701317..d14701317 100644
--- a/shared/models/accounts/account.model.ts
+++ b/shared/models/actors/account.model.ts
diff --git a/shared/models/accounts/follow.model.ts b/shared/models/actors/follow.model.ts
index cdc3da560..cdc3da560 100644
--- a/shared/models/accounts/follow.model.ts
+++ b/shared/models/actors/follow.model.ts
diff --git a/shared/models/accounts/index.ts b/shared/models/actors/index.ts
index 8fe437b81..8fe437b81 100644
--- a/shared/models/accounts/index.ts
+++ b/shared/models/actors/index.ts
diff --git a/shared/models/index.ts b/shared/models/index.ts
index faf616bfc..a88c01608 100644
--- a/shared/models/index.ts
+++ b/shared/models/index.ts
@@ -1,4 +1,4 @@
1export * from './accounts' 1export * from './actors'
2export * from './activitypub' 2export * from './activitypub'
3export * from './users' 3export * from './users'
4export * from './videos' 4export * from './videos'
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index f2b43d371..b5f459f31 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -1,4 +1,4 @@
1import { Account } from '../accounts' 1import { Account } from '../actors'
2import { VideoChannel } from '../videos/video-channel.model' 2import { VideoChannel } from '../videos/video-channel.model'
3import { UserRole } from './user-role' 3import { UserRole } from './user-role'
4 4
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 3a378419f..13b9c49b3 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -1,4 +1,4 @@
1import { Account } from '../accounts' 1import { Account } from '../actors'
2import { VideoChannel } from './video-channel.model' 2import { VideoChannel } from './video-channel.model'
3import { VideoPrivacy } from './video-privacy.enum' 3import { VideoPrivacy } from './video-privacy.enum'
4 4