diff options
author | Chocobozzz <me@florianbigard.com> | 2017-12-14 17:38:41 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2017-12-19 10:53:16 +0100 |
commit | 50d6de9c286abcb34ff4234d56d9cbb803db7665 (patch) | |
tree | f1732b27edcd05c7877a8358b8312f1e38c287ed /server | |
parent | fadf619ad61a016c1c7fc53de5a8f398a4f77519 (diff) | |
download | PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.tar.gz PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.tar.zst PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.zip |
Begin moving video channel to actor
Diffstat (limited to 'server')
80 files changed, 1721 insertions, 1992 deletions
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 @@ | |||
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers' | 3 | import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers' |
4 | import { ACTIVITY_PUB, CONFIG } from '../../initializers' | 4 | import { ACTIVITY_PUB, CONFIG } from '../../initializers' |
5 | import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send' | 5 | import { buildVideoAnnounceToFollowers } from '../../lib/activitypub/send' |
6 | import { buildVideoAnnounceToFollowers } from '../../lib/index' | ||
7 | import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares' | 6 | import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares' |
8 | import { | 7 | import { videoChannelsGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators' |
9 | videoChannelsGetValidator, | ||
10 | videoChannelsShareValidator, | ||
11 | videosGetValidator, | ||
12 | videosShareValidator | ||
13 | } from '../../middlewares/validators' | ||
14 | import { AccountModel } from '../../models/account/account' | 8 | import { AccountModel } from '../../models/account/account' |
15 | import { AccountFollowModel } from '../../models/account/account-follow' | 9 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
16 | import { VideoModel } from '../../models/video/video' | 10 | import { VideoModel } from '../../models/video/video' |
17 | import { VideoChannelModel } from '../../models/video/video-channel' | 11 | import { VideoChannelModel } from '../../models/video/video-channel' |
18 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' | ||
19 | import { VideoShareModel } from '../../models/video/video-share' | 12 | import { VideoShareModel } from '../../models/video/video-share' |
20 | 13 | ||
21 | const activityPubClientRouter = express.Router() | 14 | const activityPubClientRouter = express.Router() |
@@ -50,11 +43,6 @@ activityPubClientRouter.get('/video-channels/:id', | |||
50 | executeIfActivityPub(asyncMiddleware(videoChannelController)) | 43 | executeIfActivityPub(asyncMiddleware(videoChannelController)) |
51 | ) | 44 | ) |
52 | 45 | ||
53 | activityPubClientRouter.get('/video-channels/:id/announces/:accountId', | ||
54 | executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)), | ||
55 | executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController)) | ||
56 | ) | ||
57 | |||
58 | // --------------------------------------------------------------------------- | 46 | // --------------------------------------------------------------------------- |
59 | 47 | ||
60 | export { | 48 | export { |
@@ -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 | ||
102 | async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { | 90 | async 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 | |||
109 | async 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 | |||
5 | import { processActivities } from '../../lib/activitypub/process/process' | 5 | import { processActivities } from '../../lib/activitypub/process/process' |
6 | import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares' | 6 | import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares' |
7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' | 7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' |
8 | import { ActorModel } from '../../models/activitypub/actor' | ||
8 | 9 | ||
9 | const inboxRouter = express.Router() | 10 | const 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' | |||
3 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | 3 | import { activityPubCollectionPagination } from '../../helpers/activitypub' |
4 | import { pageToStartAndCount } from '../../helpers/core-utils' | 4 | import { pageToStartAndCount } from '../../helpers/core-utils' |
5 | import { ACTIVITY_PUB } from '../../initializers/constants' | 5 | import { ACTIVITY_PUB } from '../../initializers/constants' |
6 | import { addActivityData } from '../../lib/activitypub/send/send-add' | 6 | import { announceActivityData, createActivityData } from '../../lib/activitypub/send' |
7 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 7 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' |
8 | import { announceActivityData } from '../../lib/index' | ||
9 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' | 8 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' |
10 | import { AccountModel } from '../../models/account/account' | 9 | import { AccountModel } from '../../models/account/account' |
11 | import { VideoModel } from '../../models/video/video' | 10 | import { VideoModel } from '../../models/video/video' |
@@ -27,29 +26,30 @@ export { | |||
27 | 26 | ||
28 | async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { | 27 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers' | 3 | import { getFormattedObjects, getServerActor, loadActorUrlOrGetFromWebfinger, logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers' | 4 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' |
5 | import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub' | 5 | import { getOrCreateActorAndServerAndModel } from '../../../lib/activitypub' |
6 | import { sendUndoFollow } from '../../../lib/activitypub/send' | 6 | import { sendFollow, sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { sendFollow } from '../../../lib/index' | ||
8 | import { | 7 | import { |
9 | asyncMiddleware, | 8 | asyncMiddleware, |
10 | authenticate, | 9 | authenticate, |
@@ -17,8 +16,8 @@ import { | |||
17 | setPagination | 16 | setPagination |
18 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
19 | import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' | 18 | import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' |
20 | import { AccountModel } from '../../../models/account/account' | 19 | import { ActorModel } from '../../../models/activitypub/actor' |
21 | import { AccountFollowModel } from '../../../models/account/account-follow' | 20 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
22 | 21 | ||
23 | const serverFollowsRouter = express.Router() | 22 | const serverFollowsRouter = express.Router() |
24 | 23 | ||
@@ -38,7 +37,7 @@ serverFollowsRouter.post('/following', | |||
38 | asyncMiddleware(followRetry) | 37 | asyncMiddleware(followRetry) |
39 | ) | 38 | ) |
40 | 39 | ||
41 | serverFollowsRouter.delete('/following/:accountId', | 40 | serverFollowsRouter.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 | ||
64 | async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { | 63 | async 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 | ||
71 | async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { | 70 | async 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 | ||
78 | async function followRetry (req: express.Request, res: express.Response, next: express.NextFunction) { | 77 | async 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 | ||
113 | async function follow (fromAccount: AccountModel, targetAccount: AccountModel, targetAlreadyInDB: boolean) { | 110 | function 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 | ||
147 | async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { | 134 | async 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 | |||
165 | async 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' | |||
2 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' | 2 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' |
3 | import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' | 3 | import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' |
4 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
5 | import { createUserAccountAndChannel } from '../../lib' | 5 | import { createUserAccountAndChannel } from '../../lib/user' |
6 | import { | 6 | import { |
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { | 2 | import { UserRight, VideoAbuseCreate } from '../../../../shared' |
3 | logger, | 3 | import { getFormattedObjects, logger, retryTransactionWrapper } from '../../../helpers' |
4 | getFormattedObjects, | ||
5 | retryTransactionWrapper | ||
6 | } from '../../../helpers' | ||
7 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { sendVideoAbuse } from '../../../lib/activitypub/send' | ||
8 | import { | 6 | import { |
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' |
18 | import { VideoAbuseCreate, UserRight } from '../../../../shared' | ||
19 | import { sendVideoAbuse } from '../../../lib/index' | ||
20 | import { AccountModel } from '../../../models/account/account' | 16 | import { AccountModel } from '../../../models/account/account' |
21 | import { VideoModel } from '../../../models/video/video' | 17 | import { VideoModel } from '../../../models/video/video' |
22 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 18 | import { 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' | |||
2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' | 2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' |
3 | import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' | 3 | import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { createVideoChannel } from '../../../lib' | 5 | import { setAsyncActorKeys } from '../../../lib/activitypub' |
6 | import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' | 6 | import { createVideoChannel } from '../../../lib/video-channel' |
7 | import { | 7 | import { |
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 | ||
95 | function addVideoChannel (req: express.Request, res: express.Response) { | 95 | async 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 | ||
106 | async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 108 | async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -128,12 +130,13 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
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 | |||
160 | async function removeVideoChannel (req: express.Request, res: express.Response) { | 163 | async 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 | ||
170 | async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { | 174 | async 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' |
14 | import { getServerAccount } from '../../../helpers/utils' | 14 | import { getServerActor } from '../../../helpers/utils' |
15 | import { | 15 | import { |
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' |
24 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' | 24 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' |
25 | import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send' | 25 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' |
26 | import { sendCreateViewToVideoFollowers } from '../../../lib/index' | ||
27 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' | 26 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' |
28 | import { | 27 | import { |
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 @@ | |||
1 | import * as express from 'express' | ||
2 | import * as cors from 'cors' | 1 | import * as cors from 'cors' |
3 | import { | 2 | import * as express from 'express' |
4 | CONFIG, | 3 | import { CONFIG, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers' |
5 | STATIC_MAX_AGE, | 4 | import { VideosPreviewCache } from '../lib/cache' |
6 | STATIC_PATHS | ||
7 | } from '../initializers' | ||
8 | import { VideosPreviewCache } from '../lib' | ||
9 | import { asyncMiddleware } from '../middlewares' | 5 | import { asyncMiddleware } from '../middlewares' |
10 | 6 | ||
11 | const staticRouter = express.Router() | 7 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares' | 2 | import { asyncMiddleware } from '../middlewares' |
3 | import { webfingerValidator } from '../middlewares/validators' | 3 | import { webfingerValidator } from '../middlewares/validators' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { ActorModel } from '../models/activitypub/actor' |
5 | 5 | ||
6 | const webfingerRouter = express.Router() | 6 | const webfingerRouter = express.Router() |
7 | 7 | ||
@@ -19,16 +19,16 @@ export { | |||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
21 | function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { | 21 | function 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 @@ | |||
1 | import { ResultList } from '../../shared/models' | 1 | import { ResultList } from '../../shared/models' |
2 | import { Activity } from '../../shared/models/activitypub' | 2 | import { Activity } from '../../shared/models/activitypub' |
3 | import { ACTIVITY_PUB } from '../initializers' | 3 | import { ACTIVITY_PUB } from '../initializers' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { ActorModel } from '../models/activitypub/actor' |
5 | import { signObject } from './peertube-crypto' | 5 | import { signObject } from './peertube-crypto' |
6 | 6 | ||
7 | function activityPubContextify <T> (data: T) { | 7 | function 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 | ||
74 | function buildSignedActivity (byAccount: AccountModel, data: Object) { | 74 | function 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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | 2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
3 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './actor' | 3 | import { isActorAcceptActivityValid, isActorDeleteActivityValid, isActorFollowActivityValid } from './actor' |
4 | import { isAnnounceActivityValid } from './announce' | 4 | import { isAnnounceActivityValid } from './announce' |
5 | import { isActivityPubUrlValid } from './misc' | 5 | import { isActivityPubUrlValid } from './misc' |
6 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | 6 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' |
7 | import { isUndoActivityValid } from './undo' | 7 | import { isUndoActivityValid } from './undo' |
8 | import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' | 8 | import { isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' |
9 | import { | 9 | import { |
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 | ||
30 | const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = { | 30 | const 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 { | |||
59 | function checkCreateActivity (activity: any) { | 58 | function 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 | ||
66 | function checkAddActivity (activity: any) { | ||
67 | return isVideoTorrentAddActivityValid(activity) | ||
68 | } | ||
69 | |||
70 | function checkUpdateActivity (activity: any) { | 65 | function 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) { | |||
75 | function checkDeleteActivity (activity: any) { | 70 | function 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 | ||
81 | function checkFollowActivity (activity: any) { | 76 | function checkFollowActivity (activity: any) { |
82 | return isAccountFollowActivityValid(activity) | 77 | return isActorFollowActivityValid(activity) |
83 | } | 78 | } |
84 | 79 | ||
85 | function checkAcceptActivity (activity: any) { | 80 | function checkAcceptActivity (activity: any) { |
86 | return isAccountAcceptActivityValid(activity) | 81 | return isActorAcceptActivityValid(activity) |
87 | } | 82 | } |
88 | 83 | ||
89 | function checkAnnounceActivity (activity: any) { | 84 | function 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { Response } from 'express' | ||
1 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 4 | import { CONSTRAINTS_FIELDS } from '../../../initializers' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { isAccountNameValid } from '../accounts' | 6 | import { isAccountNameValid } from '../accounts' |
4 | import { exists, isUUIDValid } from '../misc' | 7 | import { exists, isUUIDValid } from '../misc' |
5 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 8 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' |
9 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | ||
6 | 10 | ||
7 | function isActorEndpointsObjectValid (endpointObject: any) { | 11 | function 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 | ||
29 | function isActorPreferredUsernameValid (preferredUsername: string) { | 33 | function isActorPreferredUsernameValid (preferredUsername: string) { |
30 | return isAccountNameValid(preferredUsername) | 34 | return isAccountNameValid(preferredUsername) || isVideoChannelNameValid(preferredUsername) |
35 | } | ||
36 | |||
37 | const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+') | ||
38 | function isActorNameValid (name: string) { | ||
39 | return exists(name) && validator.matches(name, actorNameRegExp) | ||
31 | } | 40 | } |
32 | 41 | ||
33 | function isActorPrivateKeyValid (privateKey: string) { | 42 | function 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 | ||
55 | function isActorFollowingCountValid (value: string) { | 70 | function 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 | ||
91 | function 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 | |||
103 | function isLocalActorNameExist (name: string, res: Response) { | ||
104 | const promise = ActorModel.loadLocalByName(name) | ||
105 | |||
106 | return isActorExist(promise, res) | ||
107 | } | ||
108 | |||
109 | async 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 | ||
78 | export { | 127 | export { |
@@ -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 @@ | |||
1 | import { isBaseActivityValid } from './misc' | 1 | import { isBaseActivityValid } from './misc' |
2 | import { isVideoTorrentAddActivityValid } from './videos' | 2 | import { isVideoTorrentCreateActivityValid } from './videos' |
3 | import { isVideoChannelCreateActivityValid } from './video-channels' | ||
4 | 3 | ||
5 | function isAnnounceActivityValid (activity: any) { | 4 | function 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 | ||
23 | function isBaseActivityValid (activity: any, type: string) { | 23 | function isBaseActivityValid (activity: any, type: string) { |
@@ -35,7 +35,23 @@ function isBaseActivityValid (activity: any, type: string) { | |||
35 | ) | 35 | ) |
36 | } | 36 | } |
37 | 37 | ||
38 | function 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 | |||
38 | export { | 53 | export { |
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 @@ | |||
1 | import { isAccountFollowActivityValid } from './actor' | 1 | import { isActorFollowActivityValid } from './actor' |
2 | import { isBaseActivityValid } from './misc' | 2 | import { isBaseActivityValid } from './misc' |
3 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | 3 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' |
4 | 4 | ||
5 | function isUndoActivityValid (activity: any) { | 5 | function 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' | |||
2 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' | 2 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' |
3 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 3 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' |
4 | 4 | ||
5 | function isVideoChannelCreateActivityValid (activity: any) { | ||
6 | return isBaseActivityValid(activity, 'Create') && | ||
7 | isVideoChannelObjectValid(activity.object) | ||
8 | } | ||
9 | |||
10 | function isVideoChannelUpdateActivityValid (activity: any) { | 5 | function 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 | ||
31 | export { | 26 | export { |
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' |
13 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
14 | 14 | ||
15 | function isVideoTorrentAddActivityValid (activity: any) { | 15 | function 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 | ||
45 | function isVideoTorrentObjectValid (video: any) { | 45 | function 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 | ||
67 | export { | 71 | export { |
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 @@ | |||
1 | import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' | 1 | import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' |
2 | import { AccountModel } from '../models/account/account' | 2 | import { ActorModel } from '../models/activitypub/actor' |
3 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' | 3 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' |
4 | import { jsig } from './custom-jsonld-signature' | 4 | import { jsig } from './custom-jsonld-signature' |
5 | import { logger } from './logger' | 5 | import { logger } from './logger' |
@@ -13,18 +13,18 @@ async function createPrivateAndPublicKeys () { | |||
13 | return { privateKey: key, publicKey } | 13 | return { privateKey: key, publicKey } |
14 | } | 14 | } |
15 | 15 | ||
16 | function isSignatureVerified (fromAccount: AccountModel, signedDocument: object) { | 16 | function 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 | ||
43 | function signObject (byAccount: AccountModel, data: any) { | 43 | function 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' | |||
3 | import { ResultList } from '../../shared' | 3 | import { ResultList } from '../../shared' |
4 | import { VideoResolution } from '../../shared/models/videos' | 4 | import { VideoResolution } from '../../shared/models/videos' |
5 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers' |
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { UserModel } from '../models/account/user' | 6 | import { UserModel } from '../models/account/user' |
7 | import { ActorModel } from '../models/activitypub/actor' | ||
8 | import { ApplicationModel } from '../models/application/application' | ||
8 | import { pseudoRandomBytesPromise } from './core-utils' | 9 | import { pseudoRandomBytesPromise } from './core-utils' |
9 | import { logger } from './logger' | 10 | import { logger } from './logger' |
10 | 11 | ||
@@ -80,18 +81,19 @@ function resetSequelizeInstance (instance: Model<any>, savedFields: object) { | |||
80 | }) | 81 | }) |
81 | } | 82 | } |
82 | 83 | ||
83 | let serverAccount: AccountModel | 84 | let serverActor: ActorModel |
84 | async function getServerAccount () { | 85 | async 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 | ||
97 | type SortType = { sortModel: any, sortValue: string } | 99 | type 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 @@ | |||
1 | import * as WebFinger from 'webfinger.js' | 1 | import * as WebFinger from 'webfinger.js' |
2 | import { WebFingerData } from '../../shared' | 2 | import { WebFingerData } from '../../shared' |
3 | import { fetchRemoteAccount } from '../lib/activitypub' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub' | 5 | import { 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 | ||
14 | async function getAccountFromWebfinger (nameWithHost: string) { | 14 | async 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 | ||
32 | export { | 24 | export { |
33 | getAccountFromWebfinger | 25 | loadActorUrlOrGetFromWebfinger |
34 | } | 26 | } |
35 | 27 | ||
36 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- |
37 | 29 | ||
30 | function 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 | |||
38 | function webfingerLookup (nameWithHost: string) { | 41 | function 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 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' | 3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' |
4 | import { FollowState } from '../../shared/models/accounts' | 4 | import { FollowState } from '../../shared/models/actors' |
5 | import { ActivityPubActorType } from '../../shared/models/activitypub' | ||
5 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { 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 |
7 | import { isTestInstance, root } from '../helpers/core-utils' | 8 | import { isTestInstance, root } from '../helpers/core-utils' |
@@ -210,7 +211,7 @@ const VIDEO_MIMETYPE_EXT = { | |||
210 | 211 | ||
211 | // --------------------------------------------------------------------------- | 212 | // --------------------------------------------------------------------------- |
212 | 213 | ||
213 | const SERVER_ACCOUNT_NAME = 'peertube' | 214 | const SERVER_ACTOR_NAME = 'peertube' |
214 | 215 | ||
215 | const ACTIVITY_PUB = { | 216 | const ACTIVITY_PUB = { |
216 | POTENTIAL_ACCEPT_HEADERS: [ | 217 | POTENTIAL_ACCEPT_HEADERS: [ |
@@ -229,6 +230,12 @@ const ACTIVITY_PUB = { | |||
229 | } | 230 | } |
230 | } | 231 | } |
231 | 232 | ||
233 | const 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' | |||
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | 4 | ||
5 | import { AccountModel } from '../models/account/account' | 5 | import { AccountModel } from '../models/account/account' |
6 | import { AccountFollowModel } from '../models/account/account-follow' | ||
7 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | 6 | import { AccountVideoRateModel } from '../models/account/account-video-rate' |
8 | import { UserModel } from '../models/account/user' | 7 | import { UserModel } from '../models/account/user' |
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
9 | import { ApplicationModel } from '../models/application/application' | 10 | import { ApplicationModel } from '../models/application/application' |
10 | import { AvatarModel } from '../models/avatar/avatar' | 11 | import { AvatarModel } from '../models/avatar/avatar' |
11 | import { JobModel } from '../models/job/job' | 12 | import { JobModel } from '../models/job/job' |
@@ -17,7 +18,6 @@ import { VideoModel } from '../models/video/video' | |||
17 | import { VideoAbuseModel } from '../models/video/video-abuse' | 18 | import { VideoAbuseModel } from '../models/video/video-abuse' |
18 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 19 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
19 | import { VideoChannelModel } from '../models/video/video-channel' | 20 | import { VideoChannelModel } from '../models/video/video-channel' |
20 | import { VideoChannelShareModel } from '../models/video/video-channel-share' | ||
21 | import { VideoFileModel } from '../models/video/video-file' | 21 | import { VideoFileModel } from '../models/video/video-file' |
22 | import { VideoShareModel } from '../models/video/video-share' | 22 | import { VideoShareModel } from '../models/video/video-share' |
23 | import { VideoTagModel } from '../models/video/video-tag' | 23 | import { VideoTagModel } from '../models/video/video-tag' |
@@ -56,6 +56,8 @@ const sequelizeTypescript = new SequelizeTypescript({ | |||
56 | async function initDatabaseModels (silent: boolean) { | 56 | async 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 @@ | |||
1 | import * as passwordGenerator from 'password-generator' | 1 | import * as passwordGenerator from 'password-generator' |
2 | import { UserRole } from '../../shared' | 2 | import { UserRole } from '../../shared' |
3 | import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers' | 3 | import { logger, mkdirpPromise, rimrafPromise } from '../helpers' |
4 | import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib' | 4 | import { createApplicationActor, createUserAccountAndChannel } from '../lib/user' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { ApplicationModel } from '../models/application/application' | 6 | import { ApplicationModel } from '../models/application/application' |
7 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 7 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
8 | import { applicationExist, clientsExist, usersExist } from './checker' | 8 | import { applicationExist, clientsExist, usersExist } from './checker' |
9 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' | 9 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants' |
10 | import { sequelizeTypescript } from './database' | 10 | import { sequelizeTypescript } from './database' |
11 | 11 | ||
12 | async function installApplication () { | 12 | async 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' | |||
5 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' | 5 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' |
6 | import { createLocalAccountWithoutKeys } from '../../lib/user' | 6 | import { createLocalAccountWithoutKeys } from '../../lib/user' |
7 | import { ApplicationModel } from '../../models/application/application' | 7 | import { ApplicationModel } from '../../models/application/application' |
8 | import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' | 8 | import { JOB_CATEGORIES, SERVER_ACTOR_NAME } from '../constants' |
9 | 9 | ||
10 | async function up (utils: { | 10 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { Transaction } from 'sequelize' | ||
3 | import * as url from 'url' | ||
4 | import { ActivityPubActor } from '../../../shared/models/activitypub' | ||
5 | import { doRequest, logger, retryTransactionWrapper } from '../../helpers' | ||
6 | import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub' | ||
7 | import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers' | ||
8 | import { AccountModel } from '../../models/account/account' | ||
9 | import { ServerModel } from '../../models/server/server' | ||
10 | |||
11 | async 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 | |||
29 | function 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 | |||
60 | async 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 | |||
104 | export { | ||
105 | getOrCreateAccountAndServer, | ||
106 | fetchRemoteAccount, | ||
107 | saveAccountAndServerIfNotExist | ||
108 | } | ||
109 | |||
110 | // --------------------------------------------------------------------------- | ||
111 | |||
112 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { Transaction } from 'sequelize' | ||
3 | import * as url from 'url' | ||
4 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' | ||
5 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' | ||
6 | import { createPrivateAndPublicKeys, doRequest, logger, retryTransactionWrapper } from '../../helpers' | ||
7 | import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub' | ||
8 | import { ACTIVITY_PUB, CONFIG, sequelizeTypescript } from '../../initializers' | ||
9 | import { AccountModel } from '../../models/account/account' | ||
10 | import { ActorModel } from '../../models/activitypub/actor' | ||
11 | import { ServerModel } from '../../models/server/server' | ||
12 | import { 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 | ||
15 | function 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 | |||
28 | async 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 | |||
62 | function 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 | |||
108 | type FetchRemoteActorResult = { | ||
109 | actor: ActorModel | ||
110 | preferredUsername: string | ||
111 | summary: string | ||
112 | attributedTo: ActivityPubAttributedTo[] | ||
113 | } | ||
114 | async 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 | |||
166 | function 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 | |||
184 | export { | ||
185 | getOrCreateActorAndServerAndModel, | ||
186 | saveActorAndServerAndModelIfNotExist, | ||
187 | fetchRemoteActor, | ||
188 | buildActorInstance, | ||
189 | setAsyncActorKeys | ||
190 | } | ||
191 | |||
192 | // --------------------------------------------------------------------------- | ||
193 | |||
194 | async 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 | |||
211 | function 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 | |||
220 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { ActorModel } from '../../models/activitypub/actor' |
3 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' | 3 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' |
4 | 4 | ||
5 | async function addFetchOutboxJob (account: AccountModel, t: Transaction) { | 5 | async 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 @@ | |||
1 | export * from './process' | 1 | export * from './process' |
2 | export * from './send' | 2 | export * from './send' |
3 | export * from './account' | 3 | export * from './actor' |
4 | export * from './fetch' | 4 | export * from './fetch' |
5 | export * from './share' | 5 | export * from './share' |
6 | export * from './video-channels' | ||
7 | export * from './videos' | 6 | export * from './videos' |
8 | export * from './url' | 7 | export * 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 @@ | |||
1 | export * from './process' | 1 | export * from './process' |
2 | export * from './process-accept' | 2 | export * from './process-accept' |
3 | export * from './process-add' | ||
4 | export * from './process-announce' | 3 | export * from './process-announce' |
5 | export * from './process-create' | 4 | export * from './process-create' |
6 | export * from './process-delete' | 5 | export * 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 @@ | |||
1 | import * as magnetUtil from 'magnet-uri' | 1 | import * as magnetUtil from 'magnet-uri' |
2 | import { VideoTorrentObject } from '../../../../shared' | 2 | import { VideoTorrentObject } from '../../../../shared' |
3 | import { VideoChannelObject } from '../../../../shared/models/activitypub/objects' | ||
4 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
5 | import { doRequest } from '../../../helpers' | 4 | import { doRequest } from '../../../helpers' |
6 | import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' | 5 | import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' |
7 | import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' | 6 | import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' |
8 | import { AccountModel } from '../../../models/account/account' | ||
9 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
10 | import { VideoChannelModel } from '../../../models/video/video-channel' | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
11 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
12 | import { VideoShareModel } from '../../../models/video/video-share' | 9 | import { VideoShareModel } from '../../../models/video/video-share' |
13 | import { getOrCreateAccountAndServer } from '../account' | 10 | import { getOrCreateActorAndServerAndModel } from '../actor' |
14 | |||
15 | function 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 | ||
28 | async function videoActivityObjectToDBAttributes ( | 12 | async 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 | ||
140 | async 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 | ||
166 | export { | 126 | export { |
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 @@ | |||
1 | import { ActivityAccept } from '../../../../shared/models/activitypub' | 1 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
2 | import { AccountModel } from '../../../models/account/account' | 2 | import { ActorModel } from '../../../models/activitypub/actor' |
3 | import { AccountFollowModel } from '../../../models/account/account-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { addFetchOutboxJob } from '../fetch' | 4 | import { addFetchOutboxJob } from '../fetch' |
5 | 5 | ||
6 | async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) { | 6 | async 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 | ||
22 | async function processAccept (account: AccountModel, targetAccount: AccountModel) { | 22 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { VideoTorrentObject } from '../../../../shared' | ||
3 | import { ActivityAdd } from '../../../../shared/models/activitypub' | ||
4 | import { VideoRateType } from '../../../../shared/models/videos' | ||
5 | import { logger, retryTransactionWrapper } from '../../../helpers' | ||
6 | import { sequelizeTypescript } from '../../../initializers' | ||
7 | import { AccountModel } from '../../../models/account/account' | ||
8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | ||
9 | import { TagModel } from '../../../models/video/tag' | ||
10 | import { VideoModel } from '../../../models/video/video' | ||
11 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
12 | import { VideoFileModel } from '../../../models/video/video-file' | ||
13 | import { getOrCreateAccountAndServer } from '../account' | ||
14 | import { getOrCreateVideoChannel } from '../video-channels' | ||
15 | import { generateThumbnailFromUrl } from '../videos' | ||
16 | import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' | ||
17 | |||
18 | async 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 | |||
36 | export { | ||
37 | processAddActivity | ||
38 | } | ||
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | async 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 | |||
69 | function 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 | |||
112 | async 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 @@ | |||
1 | import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub' | 1 | import { ActivityAnnounce } from '../../../../shared/models/activitypub' |
2 | import { logger, retryTransactionWrapper } from '../../../helpers' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
7 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
8 | import { VideoShareModel } from '../../../models/video/video-share' | 6 | import { VideoShareModel } from '../../../models/video/video-share' |
9 | import { getOrCreateAccountAndServer } from '../account' | 7 | import { getOrCreateActorAndServerAndModel } from '../actor' |
10 | import { forwardActivity } from '../send/misc' | 8 | import { forwardActivity } from '../send/misc' |
11 | import { processAddActivity } from './process-add' | ||
12 | import { processCreateActivity } from './process-create' | 9 | import { processCreateActivity } from './process-create' |
13 | 10 | ||
14 | async function processAnnounceActivity (activity: ActivityAnnounce) { | 11 | async 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 | ||
40 | function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { | 35 | function 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 | |||
49 | async 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 | |||
76 | function 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 | ||
85 | function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { | 44 | function 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 @@ | |||
1 | import { ActivityCreate, VideoChannelObject } from '../../../../shared' | 1 | import * as Bluebird from 'bluebird' |
2 | import { ActivityCreate, VideoTorrentObject } from '../../../../shared' | ||
2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' | 3 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' |
4 | import { VideoRateType } from '../../../../shared/models/videos' | ||
3 | import { logger, retryTransactionWrapper } from '../../../helpers' | 5 | import { logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { sequelizeTypescript } from '../../../initializers' | 6 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 7 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
8 | import { ActorModel } from '../../../models/activitypub/actor' | ||
9 | import { TagModel } from '../../../models/video/tag' | ||
7 | import { VideoModel } from '../../../models/video/video' | 10 | import { VideoModel } from '../../../models/video/video' |
8 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 11 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
9 | import { VideoChannelModel } from '../../../models/video/video-channel' | 12 | import { VideoFileModel } from '../../../models/video/video-file' |
10 | import { getOrCreateAccountAndServer } from '../account' | 13 | import { getOrCreateActorAndServerAndModel } from '../actor' |
11 | import { forwardActivity } from '../send/misc' | 14 | import { forwardActivity } from '../send/misc' |
12 | import { getVideoChannelActivityPubUrl } from '../url' | 15 | import { generateThumbnailFromUrl } from '../videos' |
13 | import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc' | 16 | import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' |
14 | 17 | ||
15 | async function processCreateActivity (activity: ActivityCreate) { | 18 | async 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 | ||
42 | async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) { | 45 | async 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 | |||
79 | function 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 | |||
121 | async 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 | |||
148 | async 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 | ||
51 | function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) { | 157 | function 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 | ||
78 | async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) { | 187 | async 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 | ||
97 | async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) { | 206 | function 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 | |||
112 | function 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 | |||
130 | function 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 | ||
139 | function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) { | 215 | function 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' | |||
2 | import { logger, retryTransactionWrapper } from '../../../helpers' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoChannelModel } from '../../../models/video/video-channel' | 7 | import { VideoChannelModel } from '../../../models/video/video-channel' |
7 | import { getOrCreateAccountAndServer } from '../account' | 8 | import { getOrCreateActorAndServerAndModel } from '../actor' |
8 | 9 | ||
9 | async function processDeleteActivity (activity: ActivityDelete) { | 10 | async 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 | ||
41 | async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) { | 43 | async 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 | ||
50 | async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) { | 52 | async 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 | ||
64 | async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { | 66 | async 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 | ||
73 | async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { | 75 | async 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 | ||
87 | async function processDeleteAccount (accountToRemove: AccountModel) { | 85 | async 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 | ||
96 | async function deleteRemoteAccount (accountToRemove: AccountModel) { | 94 | async 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 @@ | |||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
2 | import { logger, retryTransactionWrapper } from '../../../helpers' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { AccountFollowModel } from '../../../models/account/account-follow' | 5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { getOrCreateAccountAndServer } from '../account' | 6 | import { getOrCreateActorAndServerAndModel } from '../actor' |
7 | import { sendAccept } from '../send' | 7 | import { sendAccept } from '../send' |
8 | 8 | ||
9 | async function processFollowActivity (activity: ActivityFollow) { | 9 | async 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 | ||
24 | function processFollow (account: AccountModel, targetAccountURL: string) { | 24 | function 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 | ||
33 | async function follow (account: AccountModel, targetAccountURL: string) { | 33 | async 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 @@ | |||
1 | import { ActivityLike } from '../../../../shared/models/activitypub' | 1 | import { ActivityLike } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers' | 2 | import { retryTransactionWrapper } from '../../../helpers' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountModel } from '../../../models/account/account' | ||
5 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 4 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
6 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
7 | import { getOrCreateAccountAndServer } from '../account' | 7 | import { getOrCreateActorAndServerAndModel } from '../actor' |
8 | import { forwardActivity } from '../send/misc' | 8 | import { forwardActivity } from '../send/misc' |
9 | 9 | ||
10 | async function processLikeActivity (activity: ActivityLike) { | 10 | async 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 | ||
24 | async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) { | 24 | async 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 | ||
33 | function createVideoLike (byAccount: AccountModel, activity: ActivityLike) { | 33 | function 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' | |||
3 | import { logger, retryTransactionWrapper } from '../../../helpers' | 3 | import { logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { AccountModel } from '../../../models/account/account' | 5 | import { AccountModel } from '../../../models/account/account' |
6 | import { AccountFollowModel } from '../../../models/account/account-follow' | ||
7 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | ||
8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
8 | import { VideoModel } from '../../../models/video/video' | 9 | import { VideoModel } from '../../../models/video/video' |
9 | import { forwardActivity } from '../send/misc' | 10 | import { forwardActivity } from '../send/misc' |
10 | 11 | ||
@@ -32,21 +33,21 @@ export { | |||
32 | 33 | ||
33 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
34 | 35 | ||
35 | function processUndoLike (actor: string, activity: ActivityUndo) { | 36 | function 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 | ||
44 | function undoLike (actor: string, activity: ActivityUndo) { | 45 | function 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 | ||
68 | function processUndoDislike (actor: string, activity: ActivityUndo) { | 69 | function 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 | ||
77 | function undoDislike (actor: string, activity: ActivityUndo) { | 78 | function 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 | ||
101 | function processUndoFollow (actor: string, followActivity: ActivityFollow) { | 102 | function 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 | ||
110 | function undoFollow (actor: string, followActivity: ActivityFollow) { | 111 | function 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { VideoChannelObject, VideoTorrentObject } from '../../../../shared' | ||
3 | import { ActivityUpdate } from '../../../../shared/models/activitypub' | 2 | import { ActivityUpdate } from '../../../../shared/models/activitypub' |
4 | import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' | 3 | import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' |
5 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { AccountModel } from '../../../models/account/account' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
7 | import { TagModel } from '../../../models/video/tag' | 6 | import { TagModel } from '../../../models/video/tag' |
8 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
9 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
10 | import { VideoFileModel } from '../../../models/video/video-file' | 8 | import { VideoFileModel } from '../../../models/video/video-file' |
11 | import { getOrCreateAccountAndServer } from '../account' | 9 | import { getOrCreateActorAndServerAndModel } from '../actor' |
12 | import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' | 10 | import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' |
13 | 11 | ||
14 | async function processUpdateActivity (activity: ActivityUpdate) { | 12 | async 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 | ||
34 | function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) { | 30 | function 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 | ||
43 | async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) { | 39 | async 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 | |||
105 | async 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 | |||
114 | async 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 @@ | |||
1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | 1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
2 | import { logger } from '../../../helpers' | 2 | import { logger } from '../../../helpers' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { processAcceptActivity } from './process-accept' | 4 | import { processAcceptActivity } from './process-accept' |
5 | import { processAddActivity } from './process-add' | ||
6 | import { processAnnounceActivity } from './process-announce' | 5 | import { processAnnounceActivity } from './process-announce' |
7 | import { processCreateActivity } from './process-create' | 6 | import { processCreateActivity } from './process-create' |
8 | import { processDeleteActivity } from './process-delete' | 7 | import { processDeleteActivity } from './process-delete' |
@@ -11,9 +10,8 @@ import { processLikeActivity } from './process-like' | |||
11 | import { processUndoActivity } from './process-undo' | 10 | import { processUndoActivity } from './process-undo' |
12 | import { processUpdateActivity } from './process-update' | 11 | import { processUpdateActivity } from './process-update' |
13 | 12 | ||
14 | const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = { | 13 | const 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 | ||
26 | async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) { | 24 | async 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 @@ | |||
1 | export * from './send-accept' | 1 | export * from './send-accept' |
2 | export * from './send-add' | ||
3 | export * from './send-announce' | 2 | export * from './send-announce' |
4 | export * from './send-create' | 3 | export * from './send-create' |
5 | export * from './send-delete' | 4 | export * 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' | |||
2 | import { Activity } from '../../../../shared/models/activitypub' | 2 | import { Activity } from '../../../../shared/models/activitypub' |
3 | import { logger } from '../../../helpers' | 3 | import { logger } from '../../../helpers' |
4 | import { ACTIVITY_PUB } from '../../../initializers' | 4 | import { ACTIVITY_PUB } from '../../../initializers' |
5 | import { AccountModel } from '../../../models/account/account' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { AccountFollowModel } from '../../../models/account/account-follow' | 6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
7 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
9 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
10 | import { VideoShareModel } from '../../../models/video/video-share' | 8 | import { VideoShareModel } from '../../../models/video/video-share' |
11 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' | 9 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' |
12 | 10 | ||
13 | async function forwardActivity ( | 11 | async 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 | ||
46 | async function broadcastToFollowers ( | 44 | async 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 | ||
70 | async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) { | 68 | async 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 | ||
82 | function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) { | 80 | function 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 | ||
89 | function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) { | 87 | function 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 | |||
96 | function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) { | ||
97 | return { | ||
98 | to: accountsInvolvedInObject.map(a => a.followersUrl), | ||
99 | cc: [] | 90 | cc: [] |
100 | } | 91 | } |
101 | } | 92 | } |
102 | 93 | ||
103 | async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) { | 94 | async 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 | |||
110 | async 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 | ||
117 | async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) { | 101 | async 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 | ||
135 | async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) { | 119 | async 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 | ||
145 | export { | 129 | export { |
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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAccept } from '../../../../shared/models/activitypub' | 2 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { AccountFollowModel } from '../../../models/account/account-follow' | 4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
5 | import { getAccountFollowAcceptActivityPubUrl } from '../url' | 5 | import { getActorFollowAcceptActivityPubUrl } from '../url' |
6 | import { unicastTo } from './misc' | 6 | import { unicastTo } from './misc' |
7 | 7 | ||
8 | async function sendAccept (accountFollow: AccountFollowModel, t: Transaction) { | 8 | async 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 | ||
26 | function acceptActivityData (url: string, byAccount: AccountModel) { | 26 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAdd } from '../../../../shared/models/activitypub' | ||
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
4 | import { AccountModel } from '../../../models/account/account' | ||
5 | import { VideoModel } from '../../../models/video/video' | ||
6 | import { broadcastToFollowers, getAudience } from './misc' | ||
7 | |||
8 | async 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 | |||
17 | async 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 | |||
42 | export { | ||
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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAdd } from '../../../../shared/index' | ||
3 | import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
4 | import { AccountModel } from '../../../models/account/account' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
7 | import { getAnnounceActivityPubUrl } from '../url' | 6 | import { getAnnounceActivityPubUrl } from '../url' |
8 | import { | 7 | import { |
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' |
18 | import { addActivityData } from './send-add' | ||
19 | import { createActivityData } from './send-create' | 15 | import { createActivityData } from './send-create' |
20 | 16 | ||
21 | async function buildVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 17 | async 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 | ||
32 | async function sendVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 29 | async 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 | ||
38 | async function sendVideoAnnounceToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 35 | async 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 | |||
51 | async 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 | |||
60 | async 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 | |||
66 | async 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 | ||
77 | async function announceActivityData ( | 48 | async 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 | ||
100 | export { | 71 | export { |
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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
3 | import { getServerAccount } from '../../../helpers' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { getServerActor } from '../../../helpers' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 7 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' | 8 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' |
9 | import { | 9 | import { |
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 | ||
18 | async function sendCreateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { | 18 | async 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 | ||
27 | async function sendVideoAbuse (byAccount: AccountModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { | 28 | async 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 | ||
36 | async function sendCreateViewToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 37 | async 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 | ||
47 | async function sendCreateViewToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 48 | async 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 | ||
61 | async function sendCreateDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 62 | async 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 | ||
72 | async function sendCreateDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 73 | async 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 | ||
84 | async function createActivityData ( | 85 | async 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 | ||
105 | function createDislikeActivityData (byAccount: AccountModel, video: VideoModel) { | 106 | function 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 | ||
115 | export { | 116 | export { |
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 | ||
128 | function createViewActivityData (byAccount: AccountModel, video: VideoModel) { | 129 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityDelete } from '../../../../shared/models/activitypub' | 2 | import { ActivityDelete } from '../../../../shared/models/activitypub' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
8 | import { broadcastToFollowers } from './misc' | 6 | import { broadcastToFollowers } from './misc' |
9 | 7 | ||
10 | async 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 | |||
21 | async function sendDeleteVideo (video: VideoModel, t: Transaction) { | 8 | async 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 | ||
32 | async function sendDeleteAccount (account: AccountModel, t: Transaction) { | 19 | async 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 | ||
40 | export { | 27 | export { |
41 | sendDeleteVideoChannel, | ||
42 | sendDeleteVideo, | 28 | sendDeleteVideo, |
43 | sendDeleteAccount | 29 | sendDeleteActor |
44 | } | 30 | } |
45 | 31 | ||
46 | // --------------------------------------------------------------------------- | 32 | // --------------------------------------------------------------------------- |
47 | 33 | ||
48 | function deleteActivityData (url: string, byAccount: AccountModel): ActivityDelete { | 34 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 2 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { AccountFollowModel } from '../../../models/account/account-follow' | 4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
5 | import { getAccountFollowActivityPubUrl } from '../url' | 5 | import { getActorFollowActivityPubUrl } from '../url' |
6 | import { unicastTo } from './misc' | 6 | import { unicastTo } from './misc' |
7 | 7 | ||
8 | function sendFollow (accountFollow: AccountFollowModel, t: Transaction) { | 8 | function 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 | ||
18 | function followActivityData (url: string, byAccount: AccountModel, targetAccount: AccountModel): ActivityFollow { | 18 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { getVideoLikeActivityPubUrl } from '../url' | 5 | import { getVideoLikeActivityPubUrl } from '../url' |
6 | import { | 6 | import { |
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 | ||
15 | async function sendLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 15 | async 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 | ||
25 | async function sendLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 25 | async 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 | ||
36 | async function likeActivityData ( | 36 | async 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' |
9 | import { AccountModel } from '../../../models/account/account' | 9 | import { ActorModel } from '../../../models/activitypub/actor' |
10 | import { AccountFollowModel } from '../../../models/account/account-follow' | 10 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
11 | import { VideoModel } from '../../../models/video/video' | 11 | import { VideoModel } from '../../../models/video/video' |
12 | import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 12 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
13 | import { | 13 | import { |
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' | |||
22 | import { followActivityData } from './send-follow' | 22 | import { followActivityData } from './send-follow' |
23 | import { likeActivityData } from './send-like' | 23 | import { likeActivityData } from './send-like' |
24 | 24 | ||
25 | async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction) { | 25 | async 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 | ||
38 | async function sendUndoLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 38 | async 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 | ||
50 | async function sendUndoLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 50 | async 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 | ||
63 | async function sendUndoDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 63 | async 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 | ||
77 | async function sendUndoDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { | 77 | async 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 | ||
104 | async function undoActivityData ( | 104 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityUpdate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' |
3 | import { AccountModel } from '../../../models/account/account' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
4 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
5 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | 6 | import { VideoShareModel } from '../../../models/video/video-share' |
8 | import { getUpdateActivityPubUrl } from '../url' | 7 | import { getUpdateActivityPubUrl } from '../url' |
9 | import { broadcastToFollowers, getAudience } from './misc' | 8 | import { broadcastToFollowers, getAudience } from './misc' |
10 | 9 | ||
11 | async 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 | |||
24 | async function sendUpdateVideo (video: VideoModel, t: Transaction) { | 10 | async 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 | ||
39 | export { | 27 | export { |
40 | sendUpdateVideoChannel, | ||
41 | sendUpdateVideo | 28 | sendUpdateVideo |
42 | } | 29 | } |
43 | 30 | ||
44 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
45 | 32 | ||
46 | async function updateActivityData (url: string, byAccount: AccountModel, object: any, t: Transaction): Promise<ActivityUpdate> { | 33 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { getServerAccount } from '../../helpers' | 2 | import { getServerActor } from '../../helpers' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
5 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' | ||
6 | import { VideoShareModel } from '../../models/video/video-share' | 4 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send' | 5 | import { sendVideoAnnounceToFollowers } from './send' |
8 | |||
9 | async 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 | ||
20 | async function shareVideoByServer (video: VideoModel, t: Transaction) { | 7 | async 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 | ||
31 | export { | 18 | export { |
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 @@ | |||
1 | import { CONFIG } from '../../initializers' | 1 | import { CONFIG } from '../../initializers' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { ActorModel } from '../../models/activitypub/actor' |
3 | import { AccountFollowModel } from '../../models/account/account-follow' | 3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
7 | 6 | ||
8 | function getVideoActivityPubUrl (video: VideoModel) { | 7 | function 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 | ||
12 | function getVideoChannelActivityPubUrl (videoChannel: VideoChannelModel) { | 11 | function getVideoChannelActivityPubUrl (videoChannelUUID: string) { |
13 | return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid | 12 | return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID |
13 | } | ||
14 | |||
15 | function getApplicationActivityPubUrl () { | ||
16 | return CONFIG.WEBSERVER.URL + '/application/peertube' | ||
14 | } | 17 | } |
15 | 18 | ||
16 | function getAccountActivityPubUrl (accountName: string) { | 19 | function 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 | ||
24 | function getVideoViewActivityPubUrl (byAccount: AccountModel, video: VideoModel) { | 27 | function 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 | ||
28 | function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { | 31 | function getVideoLikeActivityPubUrl (byActor: ActorModel, video: VideoModel) { |
29 | return byAccount.url + '/likes/' + video.id | 32 | return byActor.url + '/likes/' + video.id |
30 | } | 33 | } |
31 | 34 | ||
32 | function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { | 35 | function getVideoDislikeActivityPubUrl (byActor: ActorModel, video: VideoModel) { |
33 | return byAccount.url + '/dislikes/' + video.id | 36 | return byActor.url + '/dislikes/' + video.id |
34 | } | 37 | } |
35 | 38 | ||
36 | function getAccountFollowActivityPubUrl (accountFollow: AccountFollowModel) { | 39 | function 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 | ||
43 | function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowModel) { | 46 | function 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 | ||
50 | function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountModel) { | 53 | function getAnnounceActivityPubUrl (originalUrl: string, byActor: ActorModel) { |
51 | return originalUrl + '/announces/' + byAccount.id | 54 | return originalUrl + '/announces/' + byActor.id |
52 | } | 55 | } |
53 | 56 | ||
54 | function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { | 57 | function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { |
@@ -60,12 +63,13 @@ function getUndoActivityPubUrl (originalUrl: string) { | |||
60 | } | 63 | } |
61 | 64 | ||
62 | export { | 65 | export { |
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 @@ | |||
1 | import { VideoChannelObject } from '../../../shared/models/activitypub/objects' | ||
2 | import { doRequest, logger } from '../../helpers' | ||
3 | import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub' | ||
4 | import { ACTIVITY_PUB } from '../../initializers' | ||
5 | import { AccountModel } from '../../models/account/account' | ||
6 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
7 | import { videoChannelActivityObjectToDBAttributes } from './process/misc' | ||
8 | |||
9 | async 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 | |||
24 | async 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 | |||
56 | export { | ||
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 | ||
20 | function fetchRemoteVideoPreview (video: VideoModel) { | 20 | function 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 | ||
28 | async function fetchRemoteVideoDescription (video: VideoModel) { | 28 | async 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 | ||
52 | async function sendVideoRateChangeToFollowers ( | 52 | async 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 | ||
72 | async function sendVideoRateChangeToOrigin ( | 74 | async 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 | ||
92 | export { | 96 | export { |
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 @@ | |||
1 | export * from './activitypub' | ||
2 | export * from './cache' | ||
3 | export * from './jobs' | ||
4 | export * from './oauth-model' | ||
5 | export * from './user' | ||
6 | export * 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 @@ | |||
1 | import { JobCategory } from '../../../../shared' | 1 | import { JobCategory } from '../../../../shared' |
2 | import { buildSignedActivity, logger } from '../../../helpers' | 2 | import { buildSignedActivity, logger } from '../../../helpers' |
3 | import { ACTIVITY_PUB } from '../../../initializers' | 3 | import { ACTIVITY_PUB } from '../../../initializers' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { JobHandler, JobScheduler } from '../job-scheduler' | 5 | import { JobHandler, JobScheduler } from '../job-scheduler' |
6 | 6 | ||
7 | import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' | 7 | import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' |
@@ -10,7 +10,7 @@ import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handl | |||
10 | 10 | ||
11 | type ActivityPubHttpPayload = { | 11 | type 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 | |||
44 | async function computeBody (payload: ActivityPubHttpPayload) { | 44 | async 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' | |||
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { VideoModel } from '../../../models/video/video' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { shareVideoByServer } from '../../activitypub' | 5 | import { shareVideoByServer } from '../../activitypub' |
6 | import { sendAddVideo } from '../../activitypub/send' | 6 | import { sendCreateVideo } from '../../activitypub/send' |
7 | import { JobScheduler } from '../job-scheduler' | 7 | import { JobScheduler } from '../job-scheduler' |
8 | import { TranscodingJobPayload } from './transcoding-job-scheduler' | 8 | import { 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { createPrivateAndPublicKeys, logger } from '../helpers' | 2 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
3 | import { CONFIG, sequelizeTypescript } from '../initializers' | 3 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../initializers' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { ActorModel } from '../models/activitypub/actor' | 6 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' |
7 | import { getAccountActivityPubUrl } from './activitypub' | ||
8 | import { createVideoChannel } from './video-channel' | 7 | import { createVideoChannel } from './video-channel' |
9 | 8 | ||
10 | async function createUserAccountAndChannel (user: UserModel, validateUser = true) { | 9 | async 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 | ||
39 | async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { | 34 | async 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 | ||
60 | async 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 | ||
72 | export { | 70 | export { |
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | ||
2 | import { VideoChannelCreate } from '../../shared/models' | 3 | import { VideoChannelCreate } from '../../shared/models' |
3 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
4 | import { VideoChannelModel } from '../models/video/video-channel' | 5 | import { VideoChannelModel } from '../models/video/video-channel' |
5 | import { getVideoChannelActivityPubUrl } from './activitypub' | 6 | import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub' |
6 | 7 | ||
7 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { | 8 | async 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' | |||
3 | import { ActivityPubSignature } from '../../shared' | 3 | import { ActivityPubSignature } from '../../shared' |
4 | import { isSignatureVerified, logger } from '../helpers' | 4 | import { isSignatureVerified, logger } from '../helpers' |
5 | import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers' | 5 | import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers' |
6 | import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub' | 6 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' |
7 | import { AccountModel } from '../models/account/account' | 7 | import { ActorModel } from '../models/activitypub/actor' |
8 | 8 | ||
9 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 9 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param } from 'express-validator/check' |
3 | import { getServerAccount, isTestInstance, logger } from '../../helpers' | 3 | import { getServerActor, isTestInstance, logger } from '../../helpers' |
4 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
5 | import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' | ||
6 | import { CONFIG } from '../../initializers' | 5 | import { CONFIG } from '../../initializers' |
7 | import { AccountFollowModel } from '../../models/account/account-follow' | 6 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
8 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
9 | 8 | ||
10 | const followValidator = [ | 9 | const followValidator = [ |
@@ -29,15 +28,15 @@ const followValidator = [ | |||
29 | ] | 28 | ] |
30 | 29 | ||
31 | const removeFollowingValidator = [ | 30 | const 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' | |||
3 | import { UserRight } from '../../../shared' | 3 | import { UserRight } from '../../../shared' |
4 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
5 | import { isAccountIdExist } from '../../helpers/custom-validators/accounts' | 5 | import { isAccountIdExist } from '../../helpers/custom-validators/accounts' |
6 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | 6 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
7 | import { | 7 | import { |
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' |
12 | import { UserModel } from '../../models/account/user' | 12 | import { UserModel } from '../../models/account/user' |
13 | import { VideoChannelModel } from '../../models/video/video-channel' | 13 | import { VideoChannelModel } from '../../models/video/video-channel' |
14 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' | ||
15 | import { areValidationErrors } from './utils' | 14 | import { areValidationErrors } from './utils' |
16 | 15 | ||
17 | const listVideoAccountChannelsValidator = [ | 16 | const listVideoAccountChannelsValidator = [ |
@@ -98,28 +97,6 @@ const videoChannelsGetValidator = [ | |||
98 | } | 97 | } |
99 | ] | 98 | ] |
100 | 99 | ||
101 | const 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 | ||
125 | export { | 102 | export { |
@@ -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 | ||
136 | function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { | 112 | function 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' | |||
2 | import { query } from 'express-validator/check' | 2 | import { query } from 'express-validator/check' |
3 | import { logger } from '../../helpers' | 3 | import { logger } from '../../helpers' |
4 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' | 4 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' |
5 | import { AccountModel } from '../../models/account/account' | 5 | import { ActorModel } from '../../models/activitypub/actor' |
6 | import { areValidationErrors } from './utils' | 6 | import { areValidationErrors } from './utils' |
7 | 7 | ||
8 | const webfingerValidator = [ | 8 | const 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { values } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
5 | import { FollowState } from '../../../shared/models/accounts' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | ||
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { 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 | }) | ||
26 | export 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' |
18 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | 16 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' |
19 | import { sendDeleteAccount } from '../../lib/activitypub/send' | 17 | import { sendDeleteActor } from '../../lib/activitypub/send' |
20 | import { ActorModel } from '../activitypub/actor' | 18 | import { ActorModel } from '../activitypub/actor' |
21 | import { ApplicationModel } from '../application/application' | 19 | import { ApplicationModel } from '../application/application' |
22 | import { ServerModel } from '../server/server' | 20 | import { ServerModel } from '../server/server' |
@@ -24,31 +22,30 @@ import { throwIfNotValid } from '../utils' | |||
24 | import { VideoChannelModel } from '../video/video-channel' | 22 | import { VideoChannelModel } from '../video/video-channel' |
25 | import { UserModel } from './user' | 23 | import { 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 | }) | ||
50 | export class AccountModel extends Model<AccountModel> { | 42 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { | 2 | import { |
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' |
16 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 6 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
7 | import { comparePassword, cryptPassword } from '../../helpers' | ||
17 | import { | 8 | import { |
18 | comparePassword, | 9 | isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, |
19 | cryptPassword | 10 | isUserVideoQuotaValid |
20 | } from '../../helpers' | ||
21 | import { | ||
22 | isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, | ||
23 | isUserVideoQuotaValid, isUserAutoPlayVideoValid | ||
24 | } from '../../helpers/custom-validators/users' | 11 | } from '../../helpers/custom-validators/users' |
25 | import { OAuthTokenModel } from '../oauth/oauth-token' | 12 | import { OAuthTokenModel } from '../oauth/oauth-token' |
26 | import { getSort, throwIfNotValid } from '../utils' | 13 | import { getSort, throwIfNotValid } from '../utils' |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts new file mode 100644 index 000000000..4cba05e95 --- /dev/null +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -0,0 +1,260 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { values } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
5 | import { FollowState } from '../../../shared/models/actors' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | ||
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { 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 | }) | ||
26 | export 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 @@ | |||
1 | import { values } from 'lodash' | ||
1 | import { join } from 'path' | 2 | import { join } from 'path' |
2 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
3 | import { | 4 | import { |
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' |
21 | import { ActivityPubActorType } from '../../../shared/models/activitypub' | ||
7 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | 22 | import { Avatar } from '../../../shared/models/avatars/avatar.model' |
8 | import { activityPubContextify } from '../../helpers' | 23 | import { activityPubContextify } from '../../helpers' |
9 | import { | 24 | import { |
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' |
16 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | 32 | import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' |
17 | import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 33 | import { AccountModel } from '../account/account' |
18 | import { AccountFollowModel } from '../account/account-follow' | ||
19 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
20 | import { ServerModel } from '../server/server' | 35 | import { ServerModel } from '../server/server' |
21 | import { throwIfNotValid } from '../utils' | 36 | import { throwIfNotValid } from '../utils' |
37 | import { VideoChannelModel } from '../video/video-channel' | ||
38 | import { ActorFollowModel } from './actor-follow' | ||
22 | 39 | ||
40 | enum 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 | }) |
26 | export class ActorModel extends Model<ActorModel> { | 67 | export 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 @@ | |||
1 | import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
2 | import { 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' | |||
3 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' | 3 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' |
4 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
5 | import { AccountModel } from '../account/account' | 5 | import { AccountModel } from '../account/account' |
6 | import { ServerModel } from '../server/server' | ||
7 | import { getSort, throwIfNotValid } from '../utils' | 6 | import { getSort, throwIfNotValid } from '../utils' |
8 | import { VideoModel } from './video' | 7 | import { 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { AccountModel } from '../account/account' | ||
4 | import { VideoChannelModel } from './video-channel' | ||
5 | |||
6 | enum 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 | }) | ||
44 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { | 1 | import { |
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' |
19 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' | 16 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
20 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 17 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
21 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send' | 18 | import { sendDeleteActor } from '../../lib/activitypub/send' |
22 | import { AccountModel } from '../account/account' | 19 | import { AccountModel } from '../account/account' |
23 | import { ActorModel } from '../activitypub/actor' | 20 | import { ActorModel } from '../activitypub/actor' |
24 | import { ServerModel } from '../server/server' | ||
25 | import { getSort, throwIfNotValid } from '../utils' | 21 | import { getSort, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 22 | import { VideoModel } from './video' |
27 | import { VideoChannelShareModel } from './video-channel-share' | ||
28 | 23 | ||
29 | enum ScopeNames { | 24 | enum 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 { | |||
57 | export class VideoChannelModel extends Model<VideoChannelModel> { | 72 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { AccountModel } from '../account/account' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
5 | 5 | ||
6 | enum ScopeNames { | 6 | enum 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' |
68 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' | 68 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' |
69 | import { sendDeleteVideo } from '../../lib/index' | 69 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
70 | import { AccountModel } from '../account/account' | 70 | import { AccountModel } from '../account/account' |
71 | import { AccountVideoRateModel } from '../account/account-video-rate' | 71 | import { AccountVideoRateModel } from '../account/account-video-rate' |
72 | import { ActorModel } from '../activitypub/actor' | ||
72 | import { ServerModel } from '../server/server' | 73 | import { ServerModel } from '../server/server' |
73 | import { getSort, throwIfNotValid } from '../utils' | 74 | import { getSort, throwIfNotValid } from '../utils' |
74 | import { TagModel } from './tag' | 75 | import { TagModel } from './tag' |
@@ -79,8 +80,7 @@ import { VideoShareModel } from './video-share' | |||
79 | import { VideoTagModel } from './video-tag' | 80 | import { VideoTagModel } from './video-tag' |
80 | 81 | ||
81 | enum ScopeNames { | 82 | enum 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 | ||
24 | describe('Test follows', function () { | 24 | describe('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 | ||
45 | async function unfollow (url: string, accessToken: string, id: number, expectedStatus = 204) { | 45 | async 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) |