aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-20 09:43:39 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:52 +0100
commit54141398354e6e7b94aa3065a705a1251390111c (patch)
tree8d30d1b9ea8acbe04f6d404125b04fc0c9897b70 /server
parenteb8b27c93e61a896a08923dc1ca3c87ba8cf4948 (diff)
downloadPeerTube-54141398354e6e7b94aa3065a705a1251390111c.tar.gz
PeerTube-54141398354e6e7b94aa3065a705a1251390111c.tar.zst
PeerTube-54141398354e6e7b94aa3065a705a1251390111c.zip
Refractor activity pub lib/helpers
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/inbox.ts15
-rw-r--r--server/controllers/api/server/follows.ts30
-rw-r--r--server/controllers/api/videos/channel.ts6
-rw-r--r--server/controllers/api/videos/index.ts11
-rw-r--r--server/helpers/activitypub.ts65
-rw-r--r--server/helpers/custom-validators/activitypub/activity.ts73
-rw-r--r--server/helpers/custom-validators/activitypub/announce.ts15
-rw-r--r--server/helpers/custom-validators/activitypub/index.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/undo.ts13
-rw-r--r--server/helpers/custom-validators/activitypub/video-channels.ts36
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts54
-rw-r--r--server/lib/activitypub/index.ts10
-rw-r--r--server/lib/activitypub/process/index.ts8
-rw-r--r--server/lib/activitypub/process/misc.ts (renamed from server/lib/activitypub/misc.ts)18
-rw-r--r--server/lib/activitypub/process/process-accept.ts (renamed from server/lib/activitypub/process-accept.ts)6
-rw-r--r--server/lib/activitypub/process/process-add.ts (renamed from server/lib/activitypub/process-add.ts)14
-rw-r--r--server/lib/activitypub/process/process-announce.ts (renamed from server/lib/activitypub/process-announce.ts)15
-rw-r--r--server/lib/activitypub/process/process-create.ts (renamed from server/lib/activitypub/process-create.ts)14
-rw-r--r--server/lib/activitypub/process/process-delete.ts (renamed from server/lib/activitypub/process-delete.ts)16
-rw-r--r--server/lib/activitypub/process/process-follow.ts (renamed from server/lib/activitypub/process-follow.ts)20
-rw-r--r--server/lib/activitypub/process/process-undo.ts31
-rw-r--r--server/lib/activitypub/process/process-update.ts (renamed from server/lib/activitypub/process-update.ts)18
-rw-r--r--server/lib/activitypub/send-request.ts275
-rw-r--r--server/lib/activitypub/send/index.ts7
-rw-r--r--server/lib/activitypub/send/misc.ts58
-rw-r--r--server/lib/activitypub/send/send-accept.ts34
-rw-r--r--server/lib/activitypub/send/send-add.ts38
-rw-r--r--server/lib/activitypub/send/send-announce.ts45
-rw-r--r--server/lib/activitypub/send/send-create.ts44
-rw-r--r--server/lib/activitypub/send/send-delete.ts53
-rw-r--r--server/lib/activitypub/send/send-follow.ts34
-rw-r--r--server/lib/activitypub/send/send-undo.ts39
-rw-r--r--server/lib/activitypub/send/send-update.ts55
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts3
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts2
-rw-r--r--server/lib/user.ts4
-rw-r--r--server/lib/video-channel.ts4
-rw-r--r--server/middlewares/validators/follows.ts62
-rw-r--r--server/middlewares/validators/index.ts2
-rw-r--r--server/middlewares/validators/servers.ts32
-rw-r--r--server/models/account/account-follow.ts14
-rw-r--r--server/models/account/account-interface.ts2
-rw-r--r--server/models/account/account.ts3
-rw-r--r--server/models/video/video-channel.ts16
-rw-r--r--server/models/video/video.ts7
-rw-r--r--server/tests/api/multiple-servers.ts6
46 files changed, 836 insertions, 495 deletions
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index fd3695886..807d0bdf4 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -2,12 +2,12 @@ import * as express from 'express'
2import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, ActivityType, RootActivity } from '../../../shared' 2import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, ActivityType, RootActivity } from '../../../shared'
3import { logger } from '../../helpers' 3import { logger } from '../../helpers'
4import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity' 4import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
5import { processCreateActivity, processUpdateActivity } from '../../lib' 5import { processCreateActivity, processUpdateActivity, processUndoActivity } from '../../lib'
6import { processAcceptActivity } from '../../lib/activitypub/process-accept' 6import { processAcceptActivity } from '../../lib/activitypub/process/process-accept'
7import { processAddActivity } from '../../lib/activitypub/process-add' 7import { processAddActivity } from '../../lib/activitypub/process/process-add'
8import { processAnnounceActivity } from '../../lib/activitypub/process-announce' 8import { processAnnounceActivity } from '../../lib/activitypub/process/process-announce'
9import { processDeleteActivity } from '../../lib/activitypub/process-delete' 9import { processDeleteActivity } from '../../lib/activitypub/process/process-delete'
10import { processFollowActivity } from '../../lib/activitypub/process-follow' 10import { processFollowActivity } from '../../lib/activitypub/process/process-follow'
11import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares' 11import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
12import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' 12import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
13import { AccountInstance } from '../../models/account/account-interface' 13import { AccountInstance } from '../../models/account/account-interface'
@@ -19,7 +19,8 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
19 Delete: processDeleteActivity, 19 Delete: processDeleteActivity,
20 Follow: processFollowActivity, 20 Follow: processFollowActivity,
21 Accept: processAcceptActivity, 21 Accept: processAcceptActivity,
22 Announce: processAnnounceActivity 22 Announce: processAnnounceActivity,
23 Undo: processUndoActivity
23} 24}
24 25
25const inboxRouter = express.Router() 26const inboxRouter = express.Router()
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 3d184ec1f..8fc70f34f 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -6,14 +6,16 @@ import { getServerAccount } from '../../../helpers/utils'
6import { getAccountFromWebfinger } from '../../../helpers/webfinger' 6import { getAccountFromWebfinger } from '../../../helpers/webfinger'
7import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants' 7import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants'
8import { database as db } from '../../../initializers/database' 8import { database as db } from '../../../initializers/database'
9import { sendFollow } from '../../../lib/activitypub/send-request' 9import { asyncMiddleware, paginationValidator, removeFollowingValidator, setFollowersSort, setPagination } from '../../../middlewares'
10import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../../middlewares'
11import { authenticate } from '../../../middlewares/oauth' 10import { authenticate } from '../../../middlewares/oauth'
12import { setBodyHostsPort } from '../../../middlewares/servers' 11import { setBodyHostsPort } from '../../../middlewares/servers'
13import { setFollowingSort } from '../../../middlewares/sort' 12import { setFollowingSort } from '../../../middlewares/sort'
14import { ensureUserHasRight } from '../../../middlewares/user-right' 13import { ensureUserHasRight } from '../../../middlewares/user-right'
15import { followValidator } from '../../../middlewares/validators/servers' 14import { followValidator } from '../../../middlewares/validators/follows'
16import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort' 15import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort'
16import { AccountFollowInstance } from '../../../models/index'
17import { sendFollow } from '../../../lib/index'
18import { sendUndoFollow } from '../../../lib/activitypub/send/send-undo'
17 19
18const serverFollowsRouter = express.Router() 20const serverFollowsRouter = express.Router()
19 21
@@ -33,6 +35,13 @@ serverFollowsRouter.post('/following',
33 asyncMiddleware(follow) 35 asyncMiddleware(follow)
34) 36)
35 37
38serverFollowsRouter.delete('/following/:accountId',
39 authenticate,
40 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
41 removeFollowingValidator,
42 asyncMiddleware(removeFollow)
43)
44
36serverFollowsRouter.get('/followers', 45serverFollowsRouter.get('/followers',
37 paginationValidator, 46 paginationValidator,
38 followersSortValidator, 47 followersSortValidator,
@@ -96,10 +105,12 @@ async function follow (req: express.Request, res: express.Response, next: expres
96 }, 105 },
97 transaction: t 106 transaction: t
98 }) 107 })
108 accountFollow.AccountFollowing = targetAccount
109 accountFollow.AccountFollower = fromAccount
99 110
100 // Send a notification to remote server 111 // Send a notification to remote server
101 if (accountFollow.state === 'pending') { 112 if (accountFollow.state === 'pending') {
102 await sendFollow(fromAccount, targetAccount, t) 113 await sendFollow(accountFollow, t)
103 } 114 }
104 }) 115 })
105 }) 116 })
@@ -117,6 +128,17 @@ async function follow (req: express.Request, res: express.Response, next: expres
117 return res.status(204).end() 128 return res.status(204).end()
118} 129}
119 130
131async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) {
132 const following: AccountFollowInstance = res.locals.following
133
134 await db.sequelize.transaction(async t => {
135 await sendUndoFollow(following, t)
136 await following.destroy({ transaction: t })
137 })
138
139 return res.status(204).end()
140}
141
120async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) { 142async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
121 let loadedFromDB = true 143 let loadedFromDB = true
122 let account = await db.Account.loadByNameAndHost(name, host) 144 let account = await db.Account.loadByNameAndHost(name, host)
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
index 8f3df2550..ce2656e71 100644
--- a/server/controllers/api/videos/channel.ts
+++ b/server/controllers/api/videos/channel.ts
@@ -17,7 +17,7 @@ import {
17 videoChannelsUpdateValidator 17 videoChannelsUpdateValidator
18} from '../../../middlewares' 18} from '../../../middlewares'
19import { AccountInstance, VideoChannelInstance } from '../../../models' 19import { AccountInstance, VideoChannelInstance } from '../../../models'
20import { sendUpdateVideoChannel } from '../../../lib/activitypub/send-request' 20import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
21 21
22const videoChannelRouter = express.Router() 22const videoChannelRouter = express.Router()
23 23
@@ -128,9 +128,9 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
128 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) 128 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
129 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) 129 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
130 130
131 await videoChannelInstance.save(sequelizeOptions) 131 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
132 132
133 await sendUpdateVideoChannel(videoChannelInstance, t) 133 await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
134 }) 134 })
135 135
136 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid) 136 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 22a88620a..8c9b0aa50 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -12,10 +12,11 @@ import {
12 resetSequelizeInstance, 12 resetSequelizeInstance,
13 retryTransactionWrapper 13 retryTransactionWrapper
14} from '../../../helpers' 14} from '../../../helpers'
15import { getActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub' 15import { getVideoActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub'
16import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' 16import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
17import { database as db } from '../../../initializers/database' 17import { database as db } from '../../../initializers/database'
18import { sendAddVideo, sendUpdateVideo } from '../../../lib/activitypub/send-request' 18import { sendAddVideo } from '../../../lib/activitypub/send/send-add'
19import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update'
19import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' 20import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler'
20import { 21import {
21 asyncMiddleware, 22 asyncMiddleware,
@@ -175,7 +176,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
175 channelId: res.locals.videoChannel.id 176 channelId: res.locals.videoChannel.id
176 } 177 }
177 const video = db.Video.build(videoData) 178 const video = db.Video.build(videoData)
178 video.url = getActivityPubUrl('video', video.uuid) 179 video.url = getVideoActivityPubUrl(video)
179 180
180 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 181 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
181 const videoFileHeight = await getVideoFileHeight(videoFilePath) 182 const videoFileHeight = await getVideoFileHeight(videoFilePath)
@@ -274,7 +275,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
274 if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy) 275 if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy)
275 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) 276 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
276 277
277 await videoInstance.save(sequelizeOptions) 278 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
278 279
279 if (videoInfoToUpdate.tags) { 280 if (videoInfoToUpdate.tags) {
280 const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t) 281 const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
@@ -285,7 +286,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
285 286
286 // Now we'll update the video's meta data to our friends 287 // Now we'll update the video's meta data to our friends
287 if (wasPrivateVideo === false) { 288 if (wasPrivateVideo === false) {
288 await sendUpdateVideo(videoInstance, t) 289 await sendUpdateVideo(videoInstanceUpdated, t)
289 } 290 }
290 291
291 // Video is not private anymore, send a create action to remote servers 292 // Video is not private anymore, send a create action to remote servers
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index aff58515a..9622a1801 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -9,18 +9,20 @@ import { VideoChannelObject } from '../../shared/models/activitypub/objects/vide
9import { ResultList } from '../../shared/models/result-list.model' 9import { ResultList } from '../../shared/models/result-list.model'
10import { database as db, REMOTE_SCHEME } from '../initializers' 10import { database as db, REMOTE_SCHEME } from '../initializers'
11import { ACTIVITY_PUB, CONFIG, STATIC_PATHS } from '../initializers/constants' 11import { ACTIVITY_PUB, CONFIG, STATIC_PATHS } from '../initializers/constants'
12import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/misc' 12import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/process/misc'
13import { sendVideoAnnounce } from '../lib/activitypub/send-request' 13import { sendVideoAnnounce } from '../lib/activitypub/send/send-announce'
14import { sendVideoChannelAnnounce } from '../lib/index' 14import { sendVideoChannelAnnounce } from '../lib/index'
15import { AccountFollowInstance } from '../models/account/account-follow-interface'
15import { AccountInstance } from '../models/account/account-interface' 16import { AccountInstance } from '../models/account/account-interface'
17import { VideoAbuseInstance } from '../models/video/video-abuse-interface'
16import { VideoChannelInstance } from '../models/video/video-channel-interface' 18import { VideoChannelInstance } from '../models/video/video-channel-interface'
17import { VideoInstance } from '../models/video/video-interface' 19import { VideoInstance } from '../models/video/video-interface'
18import { isRemoteAccountValid } from './custom-validators' 20import { isRemoteAccountValid } from './custom-validators'
19import { isVideoChannelObjectValid } from './custom-validators/activitypub/videos'
20import { logger } from './logger' 21import { logger } from './logger'
21import { signObject } from './peertube-crypto' 22import { signObject } from './peertube-crypto'
22import { doRequest, doRequestAndSaveToFile } from './requests' 23import { doRequest, doRequestAndSaveToFile } from './requests'
23import { getServerAccount } from './utils' 24import { getServerAccount } from './utils'
25import { isVideoChannelObjectValid } from './custom-validators/activitypub/video-channels'
24 26
25function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { 27function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) {
26 const thumbnailName = video.getThumbnailName() 28 const thumbnailName = video.getThumbnailName()
@@ -55,13 +57,46 @@ async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transactio
55 return sendVideoAnnounce(serverAccount, video, t) 57 return sendVideoAnnounce(serverAccount, video, t)
56} 58}
57 59
58function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) { 60function getVideoActivityPubUrl (video: VideoInstance) {
59 if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id 61 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
60 else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id 62}
61 else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id 63
62 else if (type === 'videoAbuse') return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + id 64function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) {
65 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid
66}
67
68function getAccountActivityPubUrl (accountName: string) {
69 return CONFIG.WEBSERVER.URL + '/account/' + accountName
70}
71
72function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
73 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
74}
75
76function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
77 const me = accountFollow.AccountFollower
78 const following = accountFollow.AccountFollowing
79
80 return me.url + '#follows/' + following.id
81}
82
83function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) {
84 const follower = accountFollow.AccountFollower
85 const me = accountFollow.AccountFollowing
86
87 return follower.url + '#accepts/follows/' + me.id
88}
89
90function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) {
91 return originalUrl + '#announces/' + byAccount.id
92}
93
94function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
95 return originalUrl + '#updates/' + updatedAt
96}
63 97
64 return '' 98function getUndoActivityPubUrl (originalUrl: string) {
99 return originalUrl + '/undo'
65} 100}
66 101
67async function getOrCreateAccount (accountUrl: string) { 102async function getOrCreateAccount (accountUrl: string) {
@@ -257,7 +292,6 @@ export {
257 fetchRemoteAccountAndCreateServer, 292 fetchRemoteAccountAndCreateServer,
258 activityPubContextify, 293 activityPubContextify,
259 activityPubCollectionPagination, 294 activityPubCollectionPagination,
260 getActivityPubUrl,
261 generateThumbnailFromUrl, 295 generateThumbnailFromUrl,
262 getOrCreateAccount, 296 getOrCreateAccount,
263 fetchRemoteVideoPreview, 297 fetchRemoteVideoPreview,
@@ -265,7 +299,16 @@ export {
265 shareVideoChannelByServer, 299 shareVideoChannelByServer,
266 shareVideoByServer, 300 shareVideoByServer,
267 getOrCreateVideoChannel, 301 getOrCreateVideoChannel,
268 buildSignedActivity 302 buildSignedActivity,
303 getVideoActivityPubUrl,
304 getVideoChannelActivityPubUrl,
305 getAccountActivityPubUrl,
306 getVideoAbuseActivityPubUrl,
307 getAccountFollowActivityPubUrl,
308 getAccountFollowAcceptActivityPubUrl,
309 getAnnounceActivityPubUrl,
310 getUpdateActivityPubUrl,
311 getUndoActivityPubUrl
269} 312}
270 313
271// --------------------------------------------------------------------------- 314// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts
index 8084cf7b0..9305e092c 100644
--- a/server/helpers/custom-validators/activitypub/activity.ts
+++ b/server/helpers/custom-validators/activitypub/activity.ts
@@ -1,11 +1,11 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
2import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' 3import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
4import { isAnnounceValid } from './announce'
3import { isActivityPubUrlValid } from './misc' 5import { isActivityPubUrlValid } from './misc'
6import { isUndoValid } from './undo'
7import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
4import { 8import {
5 isAnnounceValid,
6 isVideoChannelCreateActivityValid,
7 isVideoChannelDeleteActivityValid,
8 isVideoChannelUpdateActivityValid,
9 isVideoFlagValid, 9 isVideoFlagValid,
10 isVideoTorrentAddActivityValid, 10 isVideoTorrentAddActivityValid,
11 isVideoTorrentDeleteActivityValid, 11 isVideoTorrentDeleteActivityValid,
@@ -25,18 +25,23 @@ function isRootActivityValid (activity: any) {
25 ) 25 )
26} 26}
27 27
28const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
29 Create: checkCreateActivity,
30 Add: checkAddActivity,
31 Update: checkUpdateActivity,
32 Delete: checkDeleteActivity,
33 Follow: checkFollowActivity,
34 Accept: checkAcceptActivity,
35 Announce: checkAnnounceActivity,
36 Undo: checkUndoActivity
37}
38
28function isActivityValid (activity: any) { 39function isActivityValid (activity: any) {
29 return isVideoTorrentAddActivityValid(activity) || 40 const checker = activityCheckers[activity.type]
30 isVideoChannelCreateActivityValid(activity) || 41 // Unknown activity type
31 isVideoTorrentUpdateActivityValid(activity) || 42 if (!checker) return false
32 isVideoChannelUpdateActivityValid(activity) || 43
33 isVideoTorrentDeleteActivityValid(activity) || 44 return checker(activity)
34 isVideoChannelDeleteActivityValid(activity) ||
35 isAccountDeleteActivityValid(activity) ||
36 isAccountFollowActivityValid(activity) ||
37 isAccountAcceptActivityValid(activity) ||
38 isVideoFlagValid(activity) ||
39 isAnnounceValid(activity)
40} 45}
41 46
42// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
@@ -45,3 +50,41 @@ export {
45 isRootActivityValid, 50 isRootActivityValid,
46 isActivityValid 51 isActivityValid
47} 52}
53
54// ---------------------------------------------------------------------------
55
56function checkCreateActivity (activity: any) {
57 return isVideoChannelCreateActivityValid(activity) ||
58 isVideoFlagValid(activity)
59}
60
61function checkAddActivity (activity: any) {
62 return isVideoTorrentAddActivityValid(activity)
63}
64
65function checkUpdateActivity (activity: any) {
66 return isVideoTorrentUpdateActivityValid(activity) ||
67 isVideoChannelUpdateActivityValid(activity)
68}
69
70function checkDeleteActivity (activity: any) {
71 return isVideoTorrentDeleteActivityValid(activity) ||
72 isVideoChannelDeleteActivityValid(activity) ||
73 isAccountDeleteActivityValid(activity)
74}
75
76function checkFollowActivity (activity: any) {
77 return isAccountFollowActivityValid(activity)
78}
79
80function checkAcceptActivity (activity: any) {
81 return isAccountAcceptActivityValid(activity)
82}
83
84function checkAnnounceActivity (activity: any) {
85 return isAnnounceValid(activity)
86}
87
88function checkUndoActivity (activity: any) {
89 return isUndoValid(activity)
90}
diff --git a/server/helpers/custom-validators/activitypub/announce.ts b/server/helpers/custom-validators/activitypub/announce.ts
new file mode 100644
index 000000000..4ba99d1ea
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/announce.ts
@@ -0,0 +1,15 @@
1import { isBaseActivityValid } from './misc'
2import { isVideoTorrentAddActivityValid } from './videos'
3import { isVideoChannelCreateActivityValid } from './video-channels'
4
5function isAnnounceValid (activity: any) {
6 return isBaseActivityValid(activity, 'Announce') &&
7 (
8 isVideoChannelCreateActivityValid(activity.object) ||
9 isVideoTorrentAddActivityValid(activity.object)
10 )
11}
12
13export {
14 isAnnounceValid
15}
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts
index 0eba06a7b..6685b269f 100644
--- a/server/helpers/custom-validators/activitypub/index.ts
+++ b/server/helpers/custom-validators/activitypub/index.ts
@@ -1,5 +1,7 @@
1export * from './account' 1export * from './account'
2export * from './activity' 2export * from './activity'
3export * from './signature'
4export * from './misc' 3export * from './misc'
4export * from './signature'
5export * from './undo'
6export * from './video-channels'
5export * from './videos' 7export * from './videos'
diff --git a/server/helpers/custom-validators/activitypub/undo.ts b/server/helpers/custom-validators/activitypub/undo.ts
new file mode 100644
index 000000000..a9a2a3a41
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/undo.ts
@@ -0,0 +1,13 @@
1import { isAccountFollowActivityValid } from './account'
2import { isBaseActivityValid } from './misc'
3
4function isUndoValid (activity: any) {
5 return isBaseActivityValid(activity, 'Undo') &&
6 (
7 isAccountFollowActivityValid(activity.object)
8 )
9}
10
11export {
12 isUndoValid
13}
diff --git a/server/helpers/custom-validators/activitypub/video-channels.ts b/server/helpers/custom-validators/activitypub/video-channels.ts
new file mode 100644
index 000000000..9fd3bb149
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/video-channels.ts
@@ -0,0 +1,36 @@
1import { isDateValid, isUUIDValid } from '../misc'
2import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
3import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
4
5function isVideoChannelCreateActivityValid (activity: any) {
6 return isBaseActivityValid(activity, 'Create') &&
7 isVideoChannelObjectValid(activity.object)
8}
9
10function isVideoChannelUpdateActivityValid (activity: any) {
11 return isBaseActivityValid(activity, 'Update') &&
12 isVideoChannelObjectValid(activity.object)
13}
14
15function isVideoChannelDeleteActivityValid (activity: any) {
16 return isBaseActivityValid(activity, 'Delete')
17}
18
19function isVideoChannelObjectValid (videoChannel: any) {
20 return videoChannel.type === 'VideoChannel' &&
21 isActivityPubUrlValid(videoChannel.id) &&
22 isVideoChannelNameValid(videoChannel.name) &&
23 isVideoChannelDescriptionValid(videoChannel.content) &&
24 isDateValid(videoChannel.published) &&
25 isDateValid(videoChannel.updated) &&
26 isUUIDValid(videoChannel.uuid)
27}
28
29// ---------------------------------------------------------------------------
30
31export {
32 isVideoChannelCreateActivityValid,
33 isVideoChannelUpdateActivityValid,
34 isVideoChannelDeleteActivityValid,
35 isVideoChannelObjectValid
36}
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index 728511e3d..faeedd3df 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -1,7 +1,6 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { ACTIVITY_PUB } from '../../../initializers' 2import { ACTIVITY_PUB } from '../../../initializers'
3import { exists, isDateValid, isUUIDValid } from '../misc' 3import { exists, isDateValid, isUUIDValid } from '../misc'
4import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
5import { 4import {
6 isVideoAbuseReasonValid, 5 isVideoAbuseReasonValid,
7 isVideoDurationValid, 6 isVideoDurationValid,
@@ -28,6 +27,13 @@ function isVideoTorrentDeleteActivityValid (activity: any) {
28 return isBaseActivityValid(activity, 'Delete') 27 return isBaseActivityValid(activity, 'Delete')
29} 28}
30 29
30function isVideoFlagValid (activity: any) {
31 return isBaseActivityValid(activity, 'Create') &&
32 activity.object.type === 'Flag' &&
33 isVideoAbuseReasonValid(activity.object.content) &&
34 isActivityPubUrlValid(activity.object.object)
35}
36
31function isActivityPubVideoDurationValid (value: string) { 37function isActivityPubVideoDurationValid (value: string) {
32 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration 38 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
33 return exists(value) && 39 return exists(value) &&
@@ -57,57 +63,13 @@ function isVideoTorrentObjectValid (video: any) {
57 video.url.length !== 0 63 video.url.length !== 0
58} 64}
59 65
60function isVideoFlagValid (activity: any) {
61 return isBaseActivityValid(activity, 'Create') &&
62 activity.object.type === 'Flag' &&
63 isVideoAbuseReasonValid(activity.object.content) &&
64 isActivityPubUrlValid(activity.object.object)
65}
66
67function isAnnounceValid (activity: any) {
68 return isBaseActivityValid(activity, 'Announce') &&
69 (
70 isVideoChannelCreateActivityValid(activity.object) ||
71 isVideoTorrentAddActivityValid(activity.object)
72 )
73}
74
75function isVideoChannelCreateActivityValid (activity: any) {
76 return isBaseActivityValid(activity, 'Create') &&
77 isVideoChannelObjectValid(activity.object)
78}
79
80function isVideoChannelUpdateActivityValid (activity: any) {
81 return isBaseActivityValid(activity, 'Update') &&
82 isVideoChannelObjectValid(activity.object)
83}
84
85function isVideoChannelDeleteActivityValid (activity: any) {
86 return isBaseActivityValid(activity, 'Delete')
87}
88
89function isVideoChannelObjectValid (videoChannel: any) {
90 return videoChannel.type === 'VideoChannel' &&
91 isActivityPubUrlValid(videoChannel.id) &&
92 isVideoChannelNameValid(videoChannel.name) &&
93 isVideoChannelDescriptionValid(videoChannel.content) &&
94 isDateValid(videoChannel.published) &&
95 isDateValid(videoChannel.updated) &&
96 isUUIDValid(videoChannel.uuid)
97}
98
99// --------------------------------------------------------------------------- 66// ---------------------------------------------------------------------------
100 67
101export { 68export {
102 isVideoTorrentAddActivityValid, 69 isVideoTorrentAddActivityValid,
103 isVideoChannelCreateActivityValid,
104 isVideoTorrentUpdateActivityValid, 70 isVideoTorrentUpdateActivityValid,
105 isVideoChannelUpdateActivityValid,
106 isVideoChannelDeleteActivityValid,
107 isVideoTorrentDeleteActivityValid, 71 isVideoTorrentDeleteActivityValid,
108 isVideoFlagValid, 72 isVideoFlagValid
109 isAnnounceValid,
110 isVideoChannelObjectValid
111} 73}
112 74
113// --------------------------------------------------------------------------- 75// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts
index e08108aac..1bea0a412 100644
--- a/server/lib/activitypub/index.ts
+++ b/server/lib/activitypub/index.ts
@@ -1,8 +1,2 @@
1export * from './process-accept' 1export * from './process'
2export * from './process-add' 2export * from './send'
3export * from './process-announce'
4export * from './process-create'
5export * from './process-delete'
6export * from './process-follow'
7export * from './process-update'
8export * from './send-request'
diff --git a/server/lib/activitypub/process/index.ts b/server/lib/activitypub/process/index.ts
new file mode 100644
index 000000000..e80b46b6f
--- /dev/null
+++ b/server/lib/activitypub/process/index.ts
@@ -0,0 +1,8 @@
1export * from './process-accept'
2export * from './process-add'
3export * from './process-announce'
4export * from './process-create'
5export * from './process-delete'
6export * from './process-follow'
7export * from './process-undo'
8export * from './process-update'
diff --git a/server/lib/activitypub/misc.ts b/server/lib/activitypub/process/misc.ts
index 4c210eb10..e90a793fc 100644
--- a/server/lib/activitypub/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -1,13 +1,13 @@
1import * as magnetUtil from 'magnet-uri' 1import * as magnetUtil from 'magnet-uri'
2import { VideoTorrentObject } from '../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' 3import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object'
4import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 4import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
5import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../initializers/constants' 5import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants'
6import { AccountInstance } from '../../models/account/account-interface' 6import { AccountInstance } from '../../../models/account/account-interface'
7import { VideoChannelInstance } from '../../models/video/video-channel-interface' 7import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
8import { VideoFileAttributes } from '../../models/video/video-file-interface' 8import { VideoFileAttributes } from '../../../models/video/video-file-interface'
9import { VideoAttributes, VideoInstance } from '../../models/video/video-interface' 9import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface'
10import { VideoPrivacy } from '../../../shared/models/videos/video-privacy.enum' 10import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
11 11
12function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { 12function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) {
13 return { 13 return {
diff --git a/server/lib/activitypub/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 9e0cd4032..e159c41b5 100644
--- a/server/lib/activitypub/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,6 +1,6 @@
1import { ActivityAccept } from '../../../shared/models/activitypub/activity' 1import { ActivityAccept } from '../../../../shared/models/activitypub/activity'
2import { database as db } from '../../initializers' 2import { database as db } from '../../../initializers'
3import { AccountInstance } from '../../models/account/account-interface' 3import { AccountInstance } from '../../../models/account/account-interface'
4 4
5async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) { 5async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) {
6 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.') 6 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
diff --git a/server/lib/activitypub/process-add.ts b/server/lib/activitypub/process/process-add.ts
index e1769bee8..f064c1ab6 100644
--- a/server/lib/activitypub/process-add.ts
+++ b/server/lib/activitypub/process/process-add.ts
@@ -1,11 +1,11 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoTorrentObject } from '../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { ActivityAdd } from '../../../shared/models/activitypub/activity' 3import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
4import { generateThumbnailFromUrl, getOrCreateAccount, logger, retryTransactionWrapper } from '../../helpers' 4import { generateThumbnailFromUrl, getOrCreateAccount, logger, retryTransactionWrapper } from '../../../helpers'
5import { getOrCreateVideoChannel } from '../../helpers/activitypub' 5import { getOrCreateVideoChannel } from '../../../helpers/activitypub'
6import { database as db } from '../../initializers' 6import { database as db } from '../../../initializers'
7import { AccountInstance } from '../../models/account/account-interface' 7import { AccountInstance } from '../../../models/account/account-interface'
8import { VideoChannelInstance } from '../../models/video/video-channel-interface' 8import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
9import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 9import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
10 10
11async function processAddActivity (activity: ActivityAdd) { 11async function processAddActivity (activity: ActivityAdd) {
diff --git a/server/lib/activitypub/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index eb38aecca..656db08a9 100644
--- a/server/lib/activitypub/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -1,9 +1,9 @@
1import { ActivityAnnounce } from '../../../shared/models/activitypub/activity' 1import { ActivityAnnounce } from '../../../../shared/models/activitypub/activity'
2import { getOrCreateAccount } from '../../helpers/activitypub' 2import { getOrCreateAccount } from '../../../helpers/activitypub'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { database as db } from '../../initializers/index' 4import { database as db } from '../../../initializers/index'
5import { VideoInstance } from '../../models/index' 5import { VideoInstance } from '../../../models/index'
6import { VideoChannelInstance } from '../../models/video/video-channel-interface' 6import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
7import { processAddActivity } from './process-add' 7import { processAddActivity } from './process-add'
8import { processCreateActivity } from './process-create' 8import { processCreateActivity } from './process-create'
9 9
@@ -35,7 +35,8 @@ async function processAnnounceActivity (activity: ActivityAnnounce) {
35 'Unknown activity object type %s -> %s when announcing activity.', announcedActivity.type, announcedActivity.object.type, 35 'Unknown activity object type %s -> %s when announcing activity.', announcedActivity.type, announcedActivity.object.type,
36 { activity: activity.id } 36 { activity: activity.id }
37 ) 37 )
38 return Promise.resolve(undefined) 38
39 return undefined
39} 40}
40 41
41// --------------------------------------------------------------------------- 42// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process/process-create.ts
index de8e09adf..aac941a6c 100644
--- a/server/lib/activitypub/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,9 +1,9 @@
1import { ActivityCreate, VideoChannelObject } from '../../../shared' 1import { ActivityCreate, VideoChannelObject } from '../../../../shared'
2import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' 2import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
3import { logger, retryTransactionWrapper } from '../../helpers' 3import { logger, retryTransactionWrapper } from '../../../helpers'
4import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub' 4import { getOrCreateAccount, getVideoChannelActivityPubUrl } from '../../../helpers/activitypub'
5import { database as db } from '../../initializers' 5import { database as db } from '../../../initializers'
6import { AccountInstance } from '../../models/account/account-interface' 6import { AccountInstance } from '../../../models/account/account-interface'
7import { videoChannelActivityObjectToDBAttributes } from './misc' 7import { videoChannelActivityObjectToDBAttributes } from './misc'
8 8
9async function processCreateActivity (activity: ActivityCreate) { 9async function processCreateActivity (activity: ActivityCreate) {
@@ -47,7 +47,7 @@ function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateDa
47 47
48 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account) 48 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
49 videoChannel = db.VideoChannel.build(videoChannelData) 49 videoChannel = db.VideoChannel.build(videoChannelData)
50 videoChannel.url = getActivityPubUrl('videoChannel', videoChannel.uuid) 50 videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
51 51
52 videoChannel = await videoChannel.save({ transaction: t }) 52 videoChannel = await videoChannel.save({ transaction: t })
53 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid) 53 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
diff --git a/server/lib/activitypub/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 0d5756e9c..af5d964d4 100644
--- a/server/lib/activitypub/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -1,11 +1,11 @@
1import { ActivityDelete } from '../../../shared/models/activitypub/activity' 1import { ActivityDelete } from '../../../../shared/models/activitypub/activity'
2import { getOrCreateAccount } from '../../helpers/activitypub' 2import { getOrCreateAccount } from '../../../helpers/activitypub'
3import { retryTransactionWrapper } from '../../helpers/database-utils' 3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { database as db } from '../../initializers' 5import { database as db } from '../../../initializers'
6import { AccountInstance } from '../../models/account/account-interface' 6import { AccountInstance } from '../../../models/account/account-interface'
7import { VideoChannelInstance } from '../../models/video/video-channel-interface' 7import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
8import { VideoInstance } from '../../models/video/video-interface' 8import { VideoInstance } from '../../../models/video/video-interface'
9 9
10async function processDeleteActivity (activity: ActivityDelete) { 10async function processDeleteActivity (activity: ActivityDelete) {
11 const account = await getOrCreateAccount(activity.actor) 11 const account = await getOrCreateAccount(activity.actor)
diff --git a/server/lib/activitypub/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index a805c0757..553639580 100644
--- a/server/lib/activitypub/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -1,9 +1,9 @@
1import { ActivityFollow } from '../../../shared/models/activitypub/activity' 1import { ActivityFollow } from '../../../../shared/models/activitypub/activity'
2import { getOrCreateAccount, retryTransactionWrapper } from '../../helpers' 2import { getOrCreateAccount, retryTransactionWrapper } from '../../../helpers'
3import { database as db } from '../../initializers' 3import { database as db } from '../../../initializers'
4import { AccountInstance } from '../../models/account/account-interface' 4import { AccountInstance } from '../../../models/account/account-interface'
5import { sendAccept } from './send-request' 5import { logger } from '../../../helpers/logger'
6import { logger } from '../../helpers/logger' 6import { sendAccept } from '../send/send-accept'
7 7
8async function processFollowActivity (activity: ActivityFollow) { 8async function processFollowActivity (activity: ActivityFollow) {
9 const activityObject = activity.object 9 const activityObject = activity.object
@@ -33,10 +33,10 @@ async function follow (account: AccountInstance, targetAccountURL: string) {
33 await db.sequelize.transaction(async t => { 33 await db.sequelize.transaction(async t => {
34 const targetAccount = await db.Account.loadByUrl(targetAccountURL, t) 34 const targetAccount = await db.Account.loadByUrl(targetAccountURL, t)
35 35
36 if (targetAccount === undefined) throw new Error('Unknown account') 36 if (!targetAccount) throw new Error('Unknown account')
37 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') 37 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
38 38
39 await db.AccountFollow.findOrCreate({ 39 const [ accountFollow ] = await db.AccountFollow.findOrCreate({
40 where: { 40 where: {
41 accountId: account.id, 41 accountId: account.id,
42 targetAccountId: targetAccount.id 42 targetAccountId: targetAccount.id
@@ -48,9 +48,11 @@ async function follow (account: AccountInstance, targetAccountURL: string) {
48 }, 48 },
49 transaction: t 49 transaction: t
50 }) 50 })
51 accountFollow.AccountFollower = account
52 accountFollow.AccountFollowing = targetAccount
51 53
52 // Target sends to account he accepted the follow request 54 // Target sends to account he accepted the follow request
53 return sendAccept(targetAccount, account, t) 55 return sendAccept(accountFollow, t)
54 }) 56 })
55 57
56 logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL) 58 logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL)
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
new file mode 100644
index 000000000..5d09423e1
--- /dev/null
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -0,0 +1,31 @@
1import { ActivityUndo } from '../../../../shared/models/activitypub/activity'
2import { logger } from '../../../helpers/logger'
3import { database as db } from '../../../initializers'
4
5async function processUndoActivity (activity: ActivityUndo) {
6 const activityToUndo = activity.object
7
8 if (activityToUndo.type === 'Follow') {
9 const follower = await db.Account.loadByUrl(activity.actor)
10 const following = await db.Account.loadByUrl(activityToUndo.object)
11 const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id)
12
13 if (!accountFollow) throw new Error(`'Unknown account follow (${follower.id} -> ${following.id}.`)
14
15 await accountFollow.destroy()
16
17 return undefined
18 }
19
20 logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
21
22 return undefined
23}
24
25// ---------------------------------------------------------------------------
26
27export {
28 processUndoActivity
29}
30
31// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/process-update.ts b/server/lib/activitypub/process/process-update.ts
index a9aa5eeb4..a3bfb1baf 100644
--- a/server/lib/activitypub/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,12 +1,12 @@
1import { VideoChannelObject, VideoTorrentObject } from '../../../shared' 1import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
2import { ActivityUpdate } from '../../../shared/models/activitypub/activity' 2import { ActivityUpdate } from '../../../../shared/models/activitypub/activity'
3import { getOrCreateAccount } from '../../helpers/activitypub' 3import { getOrCreateAccount } from '../../../helpers/activitypub'
4import { retryTransactionWrapper } from '../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../../helpers/database-utils'
5import { logger } from '../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { resetSequelizeInstance } from '../../helpers/utils' 6import { resetSequelizeInstance } from '../../../helpers/utils'
7import { database as db } from '../../initializers' 7import { database as db } from '../../../initializers'
8import { AccountInstance } from '../../models/account/account-interface' 8import { AccountInstance } from '../../../models/account/account-interface'
9import { VideoInstance } from '../../models/video/video-interface' 9import { VideoInstance } from '../../../models/video/video-interface'
10import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 10import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
11import Bluebird = require('bluebird') 11import Bluebird = require('bluebird')
12 12
diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts
deleted file mode 100644
index 261ff04ab..000000000
--- a/server/lib/activitypub/send-request.ts
+++ /dev/null
@@ -1,275 +0,0 @@
1import { Transaction } from 'sequelize'
2import {
3 ActivityAccept,
4 ActivityAdd,
5 ActivityCreate,
6 ActivityDelete,
7 ActivityFollow,
8 ActivityUpdate
9} from '../../../shared/models/activitypub/activity'
10import { getActivityPubUrl } from '../../helpers/activitypub'
11import { logger } from '../../helpers/logger'
12import { database as db } from '../../initializers'
13import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../models'
14import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
15import { activitypubHttpJobScheduler } from '../jobs'
16import { ACTIVITY_PUB } from '../../initializers/constants'
17import { VideoPrivacy } from '../../../shared/models/videos/video-privacy.enum'
18
19async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
20 const byAccount = videoChannel.Account
21
22 const videoChannelObject = videoChannel.toActivityPubObject()
23 const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject)
24
25 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
26}
27
28async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
29 const byAccount = videoChannel.Account
30
31 const videoChannelObject = videoChannel.toActivityPubObject()
32 const data = await updateActivityData(videoChannel.url, byAccount, videoChannelObject)
33
34 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
35 accountsInvolved.push(byAccount)
36
37 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
38}
39
40async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
41 const byAccount = videoChannel.Account
42
43 const data = await deleteActivityData(videoChannel.url, byAccount)
44
45 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
46 accountsInvolved.push(byAccount)
47
48 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
49}
50
51async function sendAddVideo (video: VideoInstance, t: Transaction) {
52 const byAccount = video.VideoChannel.Account
53
54 const videoObject = video.toActivityPubObject()
55 const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject)
56
57 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
58}
59
60async function sendUpdateVideo (video: VideoInstance, t: Transaction) {
61 const byAccount = video.VideoChannel.Account
62
63 const videoObject = video.toActivityPubObject()
64 const data = await updateActivityData(video.url, byAccount, videoObject)
65
66 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
67 accountsInvolved.push(byAccount)
68
69 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
70}
71
72async function sendDeleteVideo (video: VideoInstance, t: Transaction) {
73 const byAccount = video.VideoChannel.Account
74
75 const data = await deleteActivityData(video.url, byAccount)
76
77 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
78 accountsInvolved.push(byAccount)
79
80 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
81}
82
83async function sendDeleteAccount (account: AccountInstance, t: Transaction) {
84 const data = await deleteActivityData(account.url, account)
85
86 return broadcastToFollowers(data, account, [ account ], t)
87}
88
89async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
90 const url = getActivityPubUrl('videoChannel', videoChannel.uuid) + '#announce'
91 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
92
93 const data = await announceActivityData(url, byAccount, announcedActivity)
94 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
95}
96
97async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
98 const url = getActivityPubUrl('video', video.uuid) + '#announce'
99
100 const videoChannel = video.VideoChannel
101 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
102
103 const data = await announceActivityData(url, byAccount, announcedActivity)
104 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
105}
106
107async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) {
108 const url = getActivityPubUrl('videoAbuse', videoAbuse.id.toString())
109 const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject())
110
111 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
112}
113
114async function sendAccept (byAccount: AccountInstance, toAccount: AccountInstance, t: Transaction) {
115 const data = await acceptActivityData(byAccount)
116
117 return unicastTo(data, byAccount, toAccount.inboxUrl, t)
118}
119
120async function sendFollow (byAccount: AccountInstance, toAccount: AccountInstance, t: Transaction) {
121 const data = await followActivityData(toAccount.url, byAccount)
122
123 return unicastTo(data, byAccount, toAccount.inboxUrl, t)
124}
125
126// ---------------------------------------------------------------------------
127
128export {
129 sendCreateVideoChannel,
130 sendUpdateVideoChannel,
131 sendDeleteVideoChannel,
132 sendAddVideo,
133 sendUpdateVideo,
134 sendDeleteVideo,
135 sendDeleteAccount,
136 sendAccept,
137 sendFollow,
138 sendVideoAbuse,
139 sendVideoChannelAnnounce,
140 sendVideoAnnounce
141}
142
143// ---------------------------------------------------------------------------
144
145async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) {
146 const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
147 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
148 if (result.data.length === 0) {
149 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
150 return undefined
151 }
152
153 const jobPayload = {
154 uris: result.data,
155 signatureAccountId: byAccount.id,
156 body: data
157 }
158
159 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
160}
161
162async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
163 const jobPayload = {
164 uris: [ toAccountUrl ],
165 signatureAccountId: byAccount.id,
166 body: data
167 }
168
169 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
170}
171
172async function getAudience (accountSender: AccountInstance, isPublic = true) {
173 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
174
175 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
176 let to = []
177 let cc = []
178
179 if (isPublic) {
180 to = [ ACTIVITY_PUB.PUBLIC ]
181 cc = followerInboxUrls
182 } else { // Unlisted
183 to = followerInboxUrls
184 cc = [ ACTIVITY_PUB.PUBLIC ]
185 }
186
187 return { to, cc }
188}
189
190async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
191 const { to, cc } = await getAudience(byAccount)
192 const activity: ActivityCreate = {
193 type: 'Create',
194 id: url,
195 actor: byAccount.url,
196 to,
197 cc,
198 object
199 }
200
201 return activity
202}
203
204async function updateActivityData (url: string, byAccount: AccountInstance, object: any) {
205 const { to, cc } = await getAudience(byAccount)
206 const activity: ActivityUpdate = {
207 type: 'Update',
208 id: url,
209 actor: byAccount.url,
210 to,
211 cc,
212 object
213 }
214
215 return activity
216}
217
218async function deleteActivityData (url: string, byAccount: AccountInstance) {
219 const activity: ActivityDelete = {
220 type: 'Delete',
221 id: url,
222 actor: byAccount.url
223 }
224
225 return activity
226}
227
228async function addActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, target: string, object: any) {
229 const videoPublic = video.privacy === VideoPrivacy.PUBLIC
230
231 const { to, cc } = await getAudience(byAccount, videoPublic)
232 const activity: ActivityAdd = {
233 type: 'Add',
234 id: url,
235 actor: byAccount.url,
236 to,
237 cc,
238 object,
239 target
240 }
241
242 return activity
243}
244
245async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
246 const activity = {
247 type: 'Announce',
248 id: url,
249 actor: byAccount.url,
250 object
251 }
252
253 return activity
254}
255
256async function followActivityData (url: string, byAccount: AccountInstance) {
257 const activity: ActivityFollow = {
258 type: 'Follow',
259 id: byAccount.url,
260 actor: byAccount.url,
261 object: url
262 }
263
264 return activity
265}
266
267async function acceptActivityData (byAccount: AccountInstance) {
268 const activity: ActivityAccept = {
269 type: 'Accept',
270 id: byAccount.url,
271 actor: byAccount.url
272 }
273
274 return activity
275}
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts
new file mode 100644
index 000000000..5f15dd4b5
--- /dev/null
+++ b/server/lib/activitypub/send/index.ts
@@ -0,0 +1,7 @@
1export * from './send-accept'
2export * from './send-add'
3export * from './send-announce'
4export * from './send-create'
5export * from './send-delete'
6export * from './send-follow'
7export * from './send-update'
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
new file mode 100644
index 000000000..bea955b67
--- /dev/null
+++ b/server/lib/activitypub/send/misc.ts
@@ -0,0 +1,58 @@
1import { Transaction } from 'sequelize'
2import { logger } from '../../../helpers/logger'
3import { ACTIVITY_PUB, database as db } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface'
5import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
6
7async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) {
8 const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
9 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
10 if (result.data.length === 0) {
11 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
12 return undefined
13 }
14
15 const jobPayload = {
16 uris: result.data,
17 signatureAccountId: byAccount.id,
18 body: data
19 }
20
21 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
22}
23
24async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
25 const jobPayload = {
26 uris: [ toAccountUrl ],
27 signatureAccountId: byAccount.id,
28 body: data
29 }
30
31 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
32}
33
34async function getAudience (accountSender: AccountInstance, isPublic = true) {
35 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
36
37 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
38 let to = []
39 let cc = []
40
41 if (isPublic) {
42 to = [ ACTIVITY_PUB.PUBLIC ]
43 cc = followerInboxUrls
44 } else { // Unlisted
45 to = followerInboxUrls
46 cc = [ ACTIVITY_PUB.PUBLIC ]
47 }
48
49 return { to, cc }
50}
51
52// ---------------------------------------------------------------------------
53
54export {
55 broadcastToFollowers,
56 unicastTo,
57 getAudience
58}
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
new file mode 100644
index 000000000..0324a30fa
--- /dev/null
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -0,0 +1,34 @@
1import { Transaction } from 'sequelize'
2import { ActivityAccept } from '../../../../shared/models/activitypub/activity'
3import { AccountInstance } from '../../../models'
4import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
5import { unicastTo } from './misc'
6import { getAccountFollowAcceptActivityPubUrl } from '../../../helpers/activitypub'
7
8async function sendAccept (accountFollow: AccountFollowInstance, t: Transaction) {
9 const follower = accountFollow.AccountFollower
10 const me = accountFollow.AccountFollowing
11
12 const url = getAccountFollowAcceptActivityPubUrl(accountFollow)
13 const data = await acceptActivityData(url, me)
14
15 return unicastTo(data, me, follower.inboxUrl, t)
16}
17
18// ---------------------------------------------------------------------------
19
20export {
21 sendAccept
22}
23
24// ---------------------------------------------------------------------------
25
26async function acceptActivityData (url: string, byAccount: AccountInstance) {
27 const activity: ActivityAccept = {
28 type: 'Accept',
29 id: url,
30 actor: byAccount.url
31 }
32
33 return activity
34}
diff --git a/server/lib/activitypub/send/send-add.ts b/server/lib/activitypub/send/send-add.ts
new file mode 100644
index 000000000..3012b7533
--- /dev/null
+++ b/server/lib/activitypub/send/send-add.ts
@@ -0,0 +1,38 @@
1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
3import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
4import { AccountInstance, VideoInstance } from '../../../models'
5import { broadcastToFollowers, getAudience } from './misc'
6
7async function sendAddVideo (video: VideoInstance, t: Transaction) {
8 const byAccount = video.VideoChannel.Account
9
10 const videoObject = video.toActivityPubObject()
11 const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject)
12
13 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
14}
15
16async function addActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, target: string, object: any) {
17 const videoPublic = video.privacy === VideoPrivacy.PUBLIC
18
19 const { to, cc } = await getAudience(byAccount, videoPublic)
20 const activity: ActivityAdd = {
21 type: 'Add',
22 id: url,
23 actor: byAccount.url,
24 to,
25 cc,
26 object,
27 target
28 }
29
30 return activity
31}
32
33// ---------------------------------------------------------------------------
34
35export {
36 addActivityData,
37 sendAddVideo
38}
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
new file mode 100644
index 000000000..b9217e4f6
--- /dev/null
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -0,0 +1,45 @@
1import { Transaction } from 'sequelize'
2import { AccountInstance, VideoInstance } from '../../../models'
3import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
4import { broadcastToFollowers } from './misc'
5import { addActivityData } from './send-add'
6import { createActivityData } from './send-create'
7import { getAnnounceActivityPubUrl } from '../../../helpers/activitypub'
8
9async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
10 const url = getAnnounceActivityPubUrl(video.url, byAccount)
11
12 const videoChannel = video.VideoChannel
13 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
14
15 const data = await announceActivityData(url, byAccount, announcedActivity)
16 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
17}
18
19async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
20 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
21 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
22
23 const data = await announceActivityData(url, byAccount, announcedActivity)
24 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
25}
26
27// ---------------------------------------------------------------------------
28
29export {
30 sendVideoAnnounce,
31 sendVideoChannelAnnounce
32}
33
34// ---------------------------------------------------------------------------
35
36async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
37 const activity = {
38 type: 'Announce',
39 id: url,
40 actor: byAccount.url,
41 object
42 }
43
44 return activity
45}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
new file mode 100644
index 000000000..66bfeee89
--- /dev/null
+++ b/server/lib/activitypub/send/send-create.ts
@@ -0,0 +1,44 @@
1import { Transaction } from 'sequelize'
2import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
3import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
4import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
5import { broadcastToFollowers, getAudience, unicastTo } from './misc'
6import { getVideoAbuseActivityPubUrl } from '../../../helpers/activitypub'
7
8async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
9 const byAccount = videoChannel.Account
10
11 const videoChannelObject = videoChannel.toActivityPubObject()
12 const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject)
13
14 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
15}
16
17async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) {
18 const url = getVideoAbuseActivityPubUrl(videoAbuse)
19 const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject())
20
21 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
22}
23
24async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
25 const { to, cc } = await getAudience(byAccount)
26 const activity: ActivityCreate = {
27 type: 'Create',
28 id: url,
29 actor: byAccount.url,
30 to,
31 cc,
32 object
33 }
34
35 return activity
36}
37
38// ---------------------------------------------------------------------------
39
40export {
41 sendCreateVideoChannel,
42 sendVideoAbuse,
43 createActivityData
44}
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
new file mode 100644
index 000000000..5be0e2d24
--- /dev/null
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -0,0 +1,53 @@
1import { Transaction } from 'sequelize'
2import { ActivityDelete } from '../../../../shared/models/activitypub/activity'
3import { database as db } from '../../../initializers'
4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
5import { broadcastToFollowers } from './misc'
6
7async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
8 const byAccount = videoChannel.Account
9
10 const data = await deleteActivityData(videoChannel.url, byAccount)
11
12 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
13 accountsInvolved.push(byAccount)
14
15 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
16}
17
18async function sendDeleteVideo (video: VideoInstance, t: Transaction) {
19 const byAccount = video.VideoChannel.Account
20
21 const data = await deleteActivityData(video.url, byAccount)
22
23 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
24 accountsInvolved.push(byAccount)
25
26 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
27}
28
29async function sendDeleteAccount (account: AccountInstance, t: Transaction) {
30 const data = await deleteActivityData(account.url, account)
31
32 return broadcastToFollowers(data, account, [ account ], t)
33}
34
35// ---------------------------------------------------------------------------
36
37export {
38 sendDeleteVideoChannel,
39 sendDeleteVideo,
40 sendDeleteAccount
41}
42
43// ---------------------------------------------------------------------------
44
45async function deleteActivityData (url: string, byAccount: AccountInstance) {
46 const activity: ActivityDelete = {
47 type: 'Delete',
48 id: url,
49 actor: byAccount.url
50 }
51
52 return activity
53}
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
new file mode 100644
index 000000000..48d641c22
--- /dev/null
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -0,0 +1,34 @@
1import { Transaction } from 'sequelize'
2import { ActivityFollow } from '../../../../shared/models/activitypub/activity'
3import { AccountInstance } from '../../../models'
4import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
5import { unicastTo } from './misc'
6import { getAccountFollowActivityPubUrl } from '../../../helpers/activitypub'
7
8async function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) {
9 const me = accountFollow.AccountFollower
10 const following = accountFollow.AccountFollowing
11
12 const url = getAccountFollowActivityPubUrl(accountFollow)
13 const data = await followActivityData(url, me, following)
14
15 return unicastTo(data, me, following.inboxUrl, t)
16}
17
18async function followActivityData (url: string, byAccount: AccountInstance, targetAccount: AccountInstance) {
19 const activity: ActivityFollow = {
20 type: 'Follow',
21 id: url,
22 actor: byAccount.url,
23 object: targetAccount.url
24 }
25
26 return activity
27}
28
29// ---------------------------------------------------------------------------
30
31export {
32 sendFollow,
33 followActivityData
34}
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
new file mode 100644
index 000000000..39da824f3
--- /dev/null
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -0,0 +1,39 @@
1import { Transaction } from 'sequelize'
2import { ActivityFollow, ActivityUndo } from '../../../../shared/models/activitypub/activity'
3import { AccountInstance } from '../../../models'
4import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
5import { unicastTo } from './misc'
6import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl } from '../../../helpers/activitypub'
7import { followActivityData } from './send-follow'
8
9async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) {
10 const me = accountFollow.AccountFollower
11 const following = accountFollow.AccountFollowing
12
13 const followUrl = getAccountFollowActivityPubUrl(accountFollow)
14 const undoUrl = getUndoActivityPubUrl(followUrl)
15
16 const object = await followActivityData(followUrl, me, following)
17 const data = await undoActivityData(undoUrl, me, object)
18
19 return unicastTo(data, me, following.inboxUrl, t)
20}
21
22// ---------------------------------------------------------------------------
23
24export {
25 sendUndoFollow
26}
27
28// ---------------------------------------------------------------------------
29
30async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow) {
31 const activity: ActivityUndo = {
32 type: 'Undo',
33 id: url,
34 actor: byAccount.url,
35 object
36 }
37
38 return activity
39}
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
new file mode 100644
index 000000000..42738f973
--- /dev/null
+++ b/server/lib/activitypub/send/send-update.ts
@@ -0,0 +1,55 @@
1import { Transaction } from 'sequelize'
2import { ActivityUpdate } from '../../../../shared/models/activitypub/activity'
3import { getUpdateActivityPubUrl } from '../../../helpers/activitypub'
4import { database as db } from '../../../initializers'
5import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
6import { broadcastToFollowers, getAudience } from './misc'
7
8async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
9 const byAccount = videoChannel.Account
10
11 const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString())
12 const videoChannelObject = videoChannel.toActivityPubObject()
13 const data = await updateActivityData(url, byAccount, videoChannelObject)
14
15 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
16 accountsInvolved.push(byAccount)
17
18 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
19}
20
21async function sendUpdateVideo (video: VideoInstance, t: Transaction) {
22 const byAccount = video.VideoChannel.Account
23
24 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
25 const videoObject = video.toActivityPubObject()
26 const data = await updateActivityData(url, byAccount, videoObject)
27
28 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
29 accountsInvolved.push(byAccount)
30
31 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
32}
33
34// ---------------------------------------------------------------------------
35
36export {
37 sendUpdateVideoChannel,
38 sendUpdateVideo
39}
40
41// ---------------------------------------------------------------------------
42
43async function updateActivityData (url: string, byAccount: AccountInstance, object: any) {
44 const { to, cc } = await getAudience(byAccount)
45 const activity: ActivityUpdate = {
46 type: 'Update',
47 id: url,
48 actor: byAccount.url,
49 to,
50 cc,
51 object
52 }
53
54 return activity
55}
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 6443899d3..f26110973 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,10 +3,11 @@ import { computeResolutionsToTranscode, logger } from '../../../helpers'
3 3
4import { database as db } from '../../../initializers/database' 4import { database as db } from '../../../initializers/database'
5import { VideoInstance } from '../../../models' 5import { VideoInstance } from '../../../models'
6import { sendAddVideo } from '../../activitypub/send-request' 6
7import { JobScheduler } from '../job-scheduler' 7import { JobScheduler } from '../job-scheduler'
8import { TranscodingJobPayload } from './transcoding-job-scheduler' 8import { TranscodingJobPayload } from './transcoding-job-scheduler'
9import { shareVideoByServer } from '../../../helpers/activitypub' 9import { shareVideoByServer } from '../../../helpers/activitypub'
10import { sendAddVideo } from '../../activitypub/send/send-add'
10 11
11async function process (data: TranscodingJobPayload, jobId: number) { 12async function process (data: TranscodingJobPayload, jobId: number) {
12 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 13 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
index 4f2ce3d24..867580200 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
@@ -2,7 +2,7 @@ import { VideoResolution } from '../../../../shared'
2import { logger } from '../../../helpers' 2import { logger } from '../../../helpers'
3import { database as db } from '../../../initializers/database' 3import { database as db } from '../../../initializers/database'
4import { VideoInstance } from '../../../models' 4import { VideoInstance } from '../../../models'
5import { sendUpdateVideo } from '../../activitypub/send-request' 5import { sendUpdateVideo } from '../../activitypub/send/send-update'
6 6
7async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) { 7async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
8 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 8 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 2d7b36b4f..d54ffc916 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,11 +1,11 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { getActivityPubUrl } from '../helpers/activitypub'
3import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' 2import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto'
4import { database as db } from '../initializers' 3import { database as db } from '../initializers'
5import { CONFIG } from '../initializers/constants' 4import { CONFIG } from '../initializers/constants'
6import { UserInstance } from '../models' 5import { UserInstance } from '../models'
7import { createVideoChannel } from './video-channel' 6import { createVideoChannel } from './video-channel'
8import { logger } from '../helpers/logger' 7import { logger } from '../helpers/logger'
8import { getAccountActivityPubUrl } from '../helpers/activitypub'
9 9
10async function createUserAccountAndChannel (user: UserInstance, validateUser = true) { 10async function createUserAccountAndChannel (user: UserInstance, validateUser = true) {
11 const { account, videoChannel } = await db.sequelize.transaction(async t => { 11 const { account, videoChannel } = await db.sequelize.transaction(async t => {
@@ -36,7 +36,7 @@ async function createUserAccountAndChannel (user: UserInstance, validateUser = t
36} 36}
37 37
38async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { 38async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) {
39 const url = getActivityPubUrl('account', name) 39 const url = getAccountActivityPubUrl(name)
40 40
41 const accountInstance = db.Account.build({ 41 const accountInstance = db.Account.build({
42 name, 42 name,
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 5bb1814ea..5235d9cb5 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { VideoChannelCreate } from '../../shared/models' 2import { VideoChannelCreate } from '../../shared/models'
3import { logger } from '../helpers' 3import { logger } from '../helpers'
4import { getActivityPubUrl } from '../helpers/activitypub'
5import { database as db } from '../initializers' 4import { database as db } from '../initializers'
6import { AccountInstance } from '../models' 5import { AccountInstance } from '../models'
6import { getVideoChannelActivityPubUrl } from '../helpers/activitypub'
7 7
8async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { 8async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) {
9 const videoChannelData = { 9 const videoChannelData = {
@@ -14,7 +14,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
14 } 14 }
15 15
16 const videoChannel = db.VideoChannel.build(videoChannelData) 16 const videoChannel = db.VideoChannel.build(videoChannelData)
17 videoChannel.set('url', getActivityPubUrl('videoChannel', videoChannel.uuid)) 17 videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel))
18 18
19 const options = { transaction: t } 19 const options = { transaction: t }
20 20
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
new file mode 100644
index 000000000..e22349726
--- /dev/null
+++ b/server/middlewares/validators/follows.ts
@@ -0,0 +1,62 @@
1import * as express from 'express'
2import { body } from 'express-validator/check'
3import { isTestInstance } from '../../helpers/core-utils'
4import { isAccountIdValid } from '../../helpers/custom-validators/activitypub/account'
5import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
6import { logger } from '../../helpers/logger'
7import { CONFIG, database as db } from '../../initializers'
8import { checkErrors } from './utils'
9import { getServerAccount } from '../../helpers/utils'
10
11const followValidator = [
12 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
13
14 (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 // Force https if the administrator wants to make friends
16 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
17 return res.status(400)
18 .json({
19 error: 'Cannot follow non HTTPS web server.'
20 })
21 .end()
22 }
23
24 logger.debug('Checking follow parameters', { parameters: req.body })
25
26 checkErrors(req, res, next)
27 }
28]
29
30const removeFollowingValidator = [
31 body('accountId').custom(isAccountIdValid).withMessage('Should have a valid account id'),
32
33 (req: express.Request, res: express.Response, next: express.NextFunction) => {
34 logger.debug('Checking follow parameters', { parameters: req.body })
35
36 checkErrors(req, res, async () => {
37 try {
38 const serverAccount = await getServerAccount()
39 const following = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
40
41 if (!following) {
42 return res.status(404)
43 .end()
44 }
45
46 res.locals.following = following
47
48 return next()
49 } catch (err) {
50 logger.error('Error in remove following validator.', err)
51 return res.sendStatus(500)
52 }
53 })
54 }
55]
56
57// ---------------------------------------------------------------------------
58
59export {
60 followValidator,
61 removeFollowingValidator
62}
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 3f5afe5b3..9840e8f65 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -2,7 +2,7 @@ export * from './account'
2export * from './oembed' 2export * from './oembed'
3export * from './activitypub' 3export * from './activitypub'
4export * from './pagination' 4export * from './pagination'
5export * from './servers' 5export * from './follows'
6export * from './sort' 6export * from './sort'
7export * from './users' 7export * from './users'
8export * from './videos' 8export * from './videos'
diff --git a/server/middlewares/validators/servers.ts b/server/middlewares/validators/servers.ts
deleted file mode 100644
index 95b69b789..000000000
--- a/server/middlewares/validators/servers.ts
+++ /dev/null
@@ -1,32 +0,0 @@
1import * as express from 'express'
2import { body } from 'express-validator/check'
3import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
4import { isTestInstance } from '../../helpers/core-utils'
5import { CONFIG } from '../../initializers/constants'
6import { logger } from '../../helpers/logger'
7import { checkErrors } from './utils'
8
9const followValidator = [
10 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
11
12 (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 // Force https if the administrator wants to make friends
14 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
15 return res.status(400)
16 .json({
17 error: 'Cannot follow non HTTPS web server.'
18 })
19 .end()
20 }
21
22 logger.debug('Checking follow parameters', { parameters: req.body })
23
24 checkErrors(req, res, next)
25 }
26]
27
28// ---------------------------------------------------------------------------
29
30export {
31 followValidator
32}
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index cc9b7c42b..f00c7dcd9 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -78,7 +78,19 @@ loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
78 where: { 78 where: {
79 accountId, 79 accountId,
80 targetAccountId 80 targetAccountId
81 } 81 },
82 include: [
83 {
84 model: AccountFollow[ 'sequelize' ].models.Account,
85 required: true,
86 as: 'AccountFollower'
87 },
88 {
89 model: AccountFollow['sequelize'].models.Account,
90 required: true,
91 as: 'AccountFollowing'
92 }
93 ]
82 } 94 }
83 95
84 return AccountFollow.findOne(query) 96 return AccountFollow.findOne(query)
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts
index 1a567fb7a..e30260f76 100644
--- a/server/models/account/account-interface.ts
+++ b/server/models/account/account-interface.ts
@@ -37,7 +37,7 @@ export interface AccountClass {
37 37
38export interface AccountAttributes { 38export interface AccountAttributes {
39 name: string 39 name: string
40 url: string 40 url?: string
41 publicKey: string 41 publicKey: string
42 privateKey: string 42 privateKey: string
43 followersCount: number 43 followersCount: number
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index faf5fa841..9a2921501 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2
3import { 2import {
4 activityPubContextify, 3 activityPubContextify,
5 isAccountFollowersCountValid, 4 isAccountFollowersCountValid,
@@ -15,7 +14,7 @@ import {
15 isUserUsernameValid 14 isUserUsernameValid
16} from '../../helpers' 15} from '../../helpers'
17import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' 16import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
18import { sendDeleteAccount } from '../../lib/activitypub/send-request' 17import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
19 18
20import { addMethodsToModel } from '../utils' 19import { addMethodsToModel } from '../utils'
21import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' 20import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index f8414d4a8..93566a5c6 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -1,17 +1,11 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers'
3import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers'
4
5import { addMethodsToModel, getSort } from '../utils'
6import {
7 VideoChannelInstance,
8 VideoChannelAttributes,
9
10 VideoChannelMethods
11} from './video-channel-interface'
12import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request'
13import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels' 3import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels'
14import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 4import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete'
6
7import { addMethodsToModel, getSort } from '../utils'
8import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface'
15 9
16let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> 10let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
17let toFormattedJSON: VideoChannelMethods.ToFormattedJSON 11let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index dc10aca1a..e2069eb0c 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -9,7 +9,6 @@ import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/v
9import { 9import {
10 createTorrentPromise, 10 createTorrentPromise,
11 generateImageFromVideoFile, 11 generateImageFromVideoFile,
12 getActivityPubUrl,
13 getVideoFileHeight, 12 getVideoFileHeight,
14 isVideoCategoryValid, 13 isVideoCategoryValid,
15 isVideoDescriptionValid, 14 isVideoDescriptionValid,
@@ -40,13 +39,13 @@ import {
40 VIDEO_LICENCES, 39 VIDEO_LICENCES,
41 VIDEO_PRIVACIES 40 VIDEO_PRIVACIES
42} from '../../initializers' 41} from '../../initializers'
43import { sendDeleteVideo } from '../../lib/activitypub/send-request'
44 42
45import { addMethodsToModel, getSort } from '../utils' 43import { addMethodsToModel, getSort } from '../utils'
46 44
47import { TagInstance } from './tag-interface' 45import { TagInstance } from './tag-interface'
48import { VideoFileInstance, VideoFileModel } from './video-file-interface' 46import { VideoFileInstance, VideoFileModel } from './video-file-interface'
49import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface' 47import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
48import { sendDeleteVideo } from '../../lib/index'
50 49
51const Buffer = safeBuffer.Buffer 50const Buffer = safeBuffer.Buffer
52 51
@@ -584,7 +583,7 @@ toActivityPubObject = function (this: VideoInstance) {
584 583
585 const videoObject: VideoTorrentObject = { 584 const videoObject: VideoTorrentObject = {
586 type: 'Video' as 'Video', 585 type: 'Video' as 'Video',
587 id: getActivityPubUrl('video', this.uuid), 586 id: this.url,
588 name: this.name, 587 name: this.name,
589 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration 588 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
590 duration: 'PT' + this.duration + 'S', 589 duration: 'PT' + this.duration + 'S',
@@ -615,7 +614,7 @@ toActivityPubObject = function (this: VideoInstance) {
615 width: THUMBNAILS_SIZE.width, 614 width: THUMBNAILS_SIZE.width,
616 height: THUMBNAILS_SIZE.height 615 height: THUMBNAILS_SIZE.height
617 }, 616 },
618 url 617 url // FIXME: needed?
619 } 618 }
620 619
621 return videoObject 620 return videoObject
diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts
index b6a57ab6d..cdbd24f56 100644
--- a/server/tests/api/multiple-servers.ts
+++ b/server/tests/api/multiple-servers.ts
@@ -316,7 +316,7 @@ describe('Test multiple servers', function () {
316 expect(video1.serverHost).to.equal('localhost:9003') 316 expect(video1.serverHost).to.equal('localhost:9003')
317 expect(video1.duration).to.equal(5) 317 expect(video1.duration).to.equal(5)
318 expect(video1.tags).to.deep.equal([ 'tag1p3' ]) 318 expect(video1.tags).to.deep.equal([ 'tag1p3' ])
319 expect(video1.author).to.equal('root') 319 expect(video1.account).to.equal('root')
320 expect(dateIsValid(video1.createdAt)).to.be.true 320 expect(dateIsValid(video1.createdAt)).to.be.true
321 expect(dateIsValid(video1.updatedAt)).to.be.true 321 expect(dateIsValid(video1.updatedAt)).to.be.true
322 322
@@ -342,7 +342,7 @@ describe('Test multiple servers', function () {
342 expect(video2.serverHost).to.equal('localhost:9003') 342 expect(video2.serverHost).to.equal('localhost:9003')
343 expect(video2.duration).to.equal(5) 343 expect(video2.duration).to.equal(5)
344 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) 344 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
345 expect(video2.author).to.equal('root') 345 expect(video2.account).to.equal('root')
346 expect(dateIsValid(video2.createdAt)).to.be.true 346 expect(dateIsValid(video2.createdAt)).to.be.true
347 expect(dateIsValid(video2.updatedAt)).to.be.true 347 expect(dateIsValid(video2.updatedAt)).to.be.true
348 348
@@ -690,7 +690,7 @@ describe('Test multiple servers', function () {
690 expect(baseVideo.licence).to.equal(video.licence) 690 expect(baseVideo.licence).to.equal(video.licence)
691 expect(baseVideo.category).to.equal(video.category) 691 expect(baseVideo.category).to.equal(video.category)
692 expect(baseVideo.nsfw).to.equal(video.nsfw) 692 expect(baseVideo.nsfw).to.equal(video.nsfw)
693 expect(baseVideo.author).to.equal(video.account) 693 expect(baseVideo.account).to.equal(video.account)
694 expect(baseVideo.tags).to.deep.equal(video.tags) 694 expect(baseVideo.tags).to.deep.equal(video.tags)
695 } 695 }
696 }) 696 })