aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-27 14:44:51 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:53 +0100
commit4e50b6a1c9a3eb261e04ede73241648e6edf21d6 (patch)
treee1c6c121d561ffc1cf2996daec03a1e7f27f0a25 /server
parent74bb2cb8348d6794ed3a0e2ec94c8c9abdde82cf (diff)
downloadPeerTube-4e50b6a1c9a3eb261e04ede73241648e6edf21d6.tar.gz
PeerTube-4e50b6a1c9a3eb261e04ede73241648e6edf21d6.tar.zst
PeerTube-4e50b6a1c9a3eb261e04ede73241648e6edf21d6.zip
Add shares forward and collection on videos/video channels
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts48
-rw-r--r--server/helpers/custom-validators/accounts.ts38
-rw-r--r--server/helpers/custom-validators/video-channels.ts33
-rw-r--r--server/helpers/custom-validators/videos.ts24
-rw-r--r--server/lib/activitypub/process/misc.ts57
-rw-r--r--server/lib/activitypub/process/process-add.ts16
-rw-r--r--server/lib/activitypub/process/process-announce.ts100
-rw-r--r--server/lib/activitypub/process/process-create.ts12
-rw-r--r--server/lib/activitypub/send/misc.ts27
-rw-r--r--server/lib/activitypub/send/send-announce.ts86
-rw-r--r--server/lib/activitypub/send/send-create.ts6
-rw-r--r--server/lib/activitypub/send/send-like.ts4
-rw-r--r--server/lib/activitypub/send/send-undo.ts4
-rw-r--r--server/lib/activitypub/share.ts6
-rw-r--r--server/lib/activitypub/url.ts14
-rw-r--r--server/middlewares/async.ts14
-rw-r--r--server/middlewares/validators/account.ts26
-rw-r--r--server/middlewares/validators/utils.ts16
-rw-r--r--server/middlewares/validators/video-channels.ts41
-rw-r--r--server/middlewares/validators/videos.ts26
-rw-r--r--server/models/video/video-channel-interface.ts2
-rw-r--r--server/models/video/video-channel-share-interface.ts2
-rw-r--r--server/models/video/video-channel-share.ts15
-rw-r--r--server/models/video/video-channel.ts17
-rw-r--r--server/models/video/video-share-interface.ts6
-rw-r--r--server/models/video/video-share.ts16
-rw-r--r--server/models/video/video.ts19
27 files changed, 542 insertions, 133 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index eee89e2fd..41272bca0 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -1,22 +1,26 @@
1// Intercept ActivityPub client requests 1// Intercept ActivityPub client requests
2import * as express from 'express' 2import * as express from 'express'
3
4import { database as db } from '../../initializers'
5import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
6import { pageToStartAndCount } from '../../helpers' 3import { pageToStartAndCount } from '../../helpers'
7import { AccountInstance, VideoChannelInstance } from '../../models'
8import { activityPubCollectionPagination } from '../../helpers/activitypub' 4import { activityPubCollectionPagination } from '../../helpers/activitypub'
5
6import { database as db } from '../../initializers'
9import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants' 7import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants'
8import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send/send-announce'
9import { buildVideoAnnounceToFollowers } from '../../lib/index'
10import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
10import { asyncMiddleware } from '../../middlewares/async' 11import { asyncMiddleware } from '../../middlewares/async'
11import { videosGetValidator } from '../../middlewares/validators/videos' 12import { videoChannelsGetValidator, videoChannelsShareValidator } from '../../middlewares/validators/video-channels'
13import { videosGetValidator, videosShareValidator } from '../../middlewares/validators/videos'
14import { AccountInstance, VideoChannelInstance } from '../../models'
15import { VideoChannelShareInstance } from '../../models/video/video-channel-share-interface'
12import { VideoInstance } from '../../models/video/video-interface' 16import { VideoInstance } from '../../models/video/video-interface'
13import { videoChannelsGetValidator } from '../../middlewares/validators/video-channels' 17import { VideoShareInstance } from '../../models/video/video-share-interface'
14 18
15const activityPubClientRouter = express.Router() 19const activityPubClientRouter = express.Router()
16 20
17activityPubClientRouter.get('/account/:name', 21activityPubClientRouter.get('/account/:name',
18 executeIfActivityPub(localAccountValidator), 22 executeIfActivityPub(localAccountValidator),
19 executeIfActivityPub(asyncMiddleware(accountController)) 23 executeIfActivityPub(accountController)
20) 24)
21 25
22activityPubClientRouter.get('/account/:name/followers', 26activityPubClientRouter.get('/account/:name/followers',
@@ -31,7 +35,12 @@ activityPubClientRouter.get('/account/:name/following',
31 35
32activityPubClientRouter.get('/videos/watch/:id', 36activityPubClientRouter.get('/videos/watch/:id',
33 executeIfActivityPub(videosGetValidator), 37 executeIfActivityPub(videosGetValidator),
34 executeIfActivityPub(asyncMiddleware(videoController)) 38 executeIfActivityPub(videoController)
39)
40
41activityPubClientRouter.get('/videos/watch/:id/announces/:accountId',
42 executeIfActivityPub(asyncMiddleware(videosShareValidator)),
43 executeIfActivityPub(asyncMiddleware(videoAnnounceController))
35) 44)
36 45
37activityPubClientRouter.get('/video-channels/:id', 46activityPubClientRouter.get('/video-channels/:id',
@@ -39,6 +48,11 @@ activityPubClientRouter.get('/video-channels/:id',
39 executeIfActivityPub(asyncMiddleware(videoChannelController)) 48 executeIfActivityPub(asyncMiddleware(videoChannelController))
40) 49)
41 50
51activityPubClientRouter.get('/video-channels/:id/announces/:accountId',
52 executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)),
53 executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController))
54)
55
42// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
43 57
44export { 58export {
@@ -47,7 +61,7 @@ export {
47 61
48// --------------------------------------------------------------------------- 62// ---------------------------------------------------------------------------
49 63
50async function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { 64function accountController (req: express.Request, res: express.Response, next: express.NextFunction) {
51 const account: AccountInstance = res.locals.account 65 const account: AccountInstance = res.locals.account
52 66
53 return res.json(account.toActivityPubObject()).end() 67 return res.json(account.toActivityPubObject()).end()
@@ -77,12 +91,26 @@ async function accountFollowingController (req: express.Request, res: express.Re
77 return res.json(activityPubResult) 91 return res.json(activityPubResult)
78} 92}
79 93
80async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { 94function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
81 const video: VideoInstance = res.locals.video 95 const video: VideoInstance = res.locals.video
82 96
83 return res.json(video.toActivityPubObject()) 97 return res.json(video.toActivityPubObject())
84} 98}
85 99
100async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
101 const share = res.locals.videoShare as VideoShareInstance
102 const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined)
103
104 return res.json(object)
105}
106
107async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
108 const share = res.locals.videoChannelShare as VideoChannelShareInstance
109 const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined)
110
111 return res.json(object)
112}
113
86async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) { 114async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) {
87 const videoChannel: VideoChannelInstance = res.locals.videoChannel 115 const videoChannel: VideoChannelInstance = res.locals.videoChannel
88 116
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts
index fe0fc650a..a6d7f2b82 100644
--- a/server/helpers/custom-validators/accounts.ts
+++ b/server/helpers/custom-validators/accounts.ts
@@ -1,4 +1,4 @@
1import * as Promise from 'bluebird' 1import * as Bluebird from 'bluebird'
2import * as express from 'express' 2import * as express from 'express'
3import 'express-validator' 3import 'express-validator'
4import * as validator from 'validator' 4import * as validator from 'validator'
@@ -11,33 +11,45 @@ function isAccountNameValid (value: string) {
11 return isUserUsernameValid(value) 11 return isUserUsernameValid(value)
12} 12}
13 13
14function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { 14function checkAccountIdExists (id: number | string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
15 let promise: Promise<AccountInstance> 15 let promise: Bluebird<AccountInstance>
16 if (validator.isInt(id)) { 16
17 if (validator.isInt('' + id)) {
17 promise = db.Account.load(+id) 18 promise = db.Account.load(+id)
18 } else { // UUID 19 } else { // UUID
19 promise = db.Account.loadByUUID(id) 20 promise = db.Account.loadByUUID('' + id)
20 } 21 }
21 22
22 promise.then(account => { 23 return checkAccountExists(promise, res, callback)
24}
25
26function checkLocalAccountNameExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
27 const p = db.Account.loadLocalByName(name)
28
29 return checkAccountExists(p, res, callback)
30}
31
32function checkAccountExists (p: Bluebird<AccountInstance>, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
33 p.then(account => {
23 if (!account) { 34 if (!account) {
24 return res.status(404) 35 return res.status(404)
25 .json({ error: 'Video account not found' }) 36 .send({ error: 'Account not found' })
26 .end() 37 .end()
27 } 38 }
28 39
29 res.locals.account = account 40 res.locals.account = account
30 callback() 41 return callback(null, account)
31 })
32 .catch(err => {
33 logger.error('Error in video account request validator.', err)
34 return res.sendStatus(500)
35 }) 42 })
43 .catch(err => {
44 logger.error('Error in account request validator.', err)
45 return res.sendStatus(500)
46 })
36} 47}
37 48
38// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
39 50
40export { 51export {
41 checkVideoAccountExists, 52 checkAccountIdExists,
53 checkLocalAccountNameExists,
42 isAccountNameValid 54 isAccountNameValid
43} 55}
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts
index 5de01f74b..267d987fc 100644
--- a/server/helpers/custom-validators/video-channels.ts
+++ b/server/helpers/custom-validators/video-channels.ts
@@ -1,14 +1,14 @@
1import * as Promise from 'bluebird' 1import * as Bluebird from 'bluebird'
2import * as validator from 'validator'
3import * as express from 'express' 2import * as express from 'express'
4import 'express-validator' 3import 'express-validator'
5import 'multer' 4import 'multer'
5import * as validator from 'validator'
6 6
7import { database as db, CONSTRAINTS_FIELDS } from '../../initializers' 7import { CONSTRAINTS_FIELDS, database as db } from '../../initializers'
8import { VideoChannelInstance } from '../../models' 8import { VideoChannelInstance } from '../../models'
9import { logger } from '../logger' 9import { logger } from '../logger'
10import { exists } from './misc'
11import { isActivityPubUrlValid } from './index' 10import { isActivityPubUrlValid } from './index'
11import { exists } from './misc'
12 12
13const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS 13const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
14 14
@@ -25,7 +25,7 @@ function isVideoChannelNameValid (value: string) {
25} 25}
26 26
27function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) { 27function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
28 let promise: Promise<VideoChannelInstance> 28 let promise: Bluebird<VideoChannelInstance>
29 if (validator.isInt(id)) { 29 if (validator.isInt(id)) {
30 promise = db.VideoChannel.loadAndPopulateAccount(+id) 30 promise = db.VideoChannel.loadAndPopulateAccount(+id)
31 } else { // UUID 31 } else { // UUID
@@ -48,11 +48,32 @@ function checkVideoChannelExists (id: string, res: express.Response, callback: (
48 }) 48 })
49} 49}
50 50
51async function isVideoChannelExistsPromise (id: string, res: express.Response) {
52 let videoChannel: VideoChannelInstance
53 if (validator.isInt(id)) {
54 videoChannel = await db.VideoChannel.loadAndPopulateAccount(+id)
55 } else { // UUID
56 videoChannel = await db.VideoChannel.loadByUUIDAndPopulateAccount(id)
57 }
58
59 if (!videoChannel) {
60 res.status(404)
61 .json({ error: 'Video channel not found' })
62 .end()
63
64 return false
65 }
66
67 res.locals.videoChannel = videoChannel
68 return true
69}
70
51// --------------------------------------------------------------------------- 71// ---------------------------------------------------------------------------
52 72
53export { 73export {
54 isVideoChannelDescriptionValid, 74 isVideoChannelDescriptionValid,
55 isVideoChannelNameValid,
56 checkVideoChannelExists, 75 checkVideoChannelExists,
76 isVideoChannelNameValid,
77 isVideoChannelExistsPromise,
57 isVideoChannelUrlValid 78 isVideoChannelUrlValid
58} 79}
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 205d8c62f..276354626 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -130,6 +130,27 @@ function checkVideoExists (id: string, res: Response, callback: () => void) {
130 }) 130 })
131} 131}
132 132
133async function isVideoExistsPromise (id: string, res: Response) {
134 let video: VideoInstance
135
136 if (validator.isInt(id)) {
137 video = await db.Video.loadAndPopulateAccountAndServerAndTags(+id)
138 } else { // UUID
139 video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id)
140 }
141
142 if (!video) {
143 res.status(404)
144 .json({ error: 'Video not found' })
145 .end()
146
147 return false
148 }
149
150 res.locals.video = video
151 return true
152}
153
133// --------------------------------------------------------------------------- 154// ---------------------------------------------------------------------------
134 155
135export { 156export {
@@ -152,5 +173,6 @@ export {
152 isVideoPrivacyValid, 173 isVideoPrivacyValid,
153 isVideoFileResolutionValid, 174 isVideoFileResolutionValid,
154 isVideoFileSizeValid, 175 isVideoFileSizeValid,
155 checkVideoExists 176 checkVideoExists,
177 isVideoExistsPromise
156} 178}
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index eefbe2884..f20e588ab 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -1,13 +1,16 @@
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 { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
4import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' 5import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
6import { doRequest } from '../../../helpers/requests'
7import { database as db } from '../../../initializers'
5import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants' 8import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants'
6import { AccountInstance } from '../../../models/account/account-interface' 9import { AccountInstance } from '../../../models/account/account-interface'
7import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 10import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
8import { VideoFileAttributes } from '../../../models/video/video-file-interface' 11import { VideoFileAttributes } from '../../../models/video/video-file-interface'
9import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface' 12import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface'
10import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' 13import { getOrCreateAccountAndServer } from '../account'
11 14
12function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { 15function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) {
13 return { 16 return {
@@ -97,10 +100,60 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO
97 return attributes 100 return attributes
98} 101}
99 102
103async function addVideoShares (instance: VideoInstance, shares: string[]) {
104 for (const share of shares) {
105 // Fetch url
106 const json = await doRequest({
107 uri: share,
108 json: true
109 })
110 const actor = json['actor']
111 if (!actor) continue
112
113 const account = await getOrCreateAccountAndServer(actor)
114
115 const entry = {
116 accountId: account.id,
117 videoId: instance.id
118 }
119
120 await db.VideoShare.findOrCreate({
121 where: entry,
122 defaults: entry
123 })
124 }
125}
126
127async function addVideoChannelShares (instance: VideoChannelInstance, shares: string[]) {
128 for (const share of shares) {
129 // Fetch url
130 const json = await doRequest({
131 uri: share,
132 json: true
133 })
134 const actor = json['actor']
135 if (!actor) continue
136
137 const account = await getOrCreateAccountAndServer(actor)
138
139 const entry = {
140 accountId: account.id,
141 videoChannelId: instance.id
142 }
143
144 await db.VideoChannelShare.findOrCreate({
145 where: entry,
146 defaults: entry
147 })
148 }
149}
150
100// --------------------------------------------------------------------------- 151// ---------------------------------------------------------------------------
101 152
102export { 153export {
103 videoFileActivityUrlToDBAttributes, 154 videoFileActivityUrlToDBAttributes,
104 videoActivityObjectToDBAttributes, 155 videoActivityObjectToDBAttributes,
105 videoChannelActivityObjectToDBAttributes 156 videoChannelActivityObjectToDBAttributes,
157 addVideoChannelShares,
158 addVideoShares
106} 159}
diff --git a/server/lib/activitypub/process/process-add.ts b/server/lib/activitypub/process/process-add.ts
index 98280b9f0..e6bf63eb2 100644
--- a/server/lib/activitypub/process/process-add.ts
+++ b/server/lib/activitypub/process/process-add.ts
@@ -11,7 +11,7 @@ import { VideoInstance } from '../../../models/video/video-interface'
11import { getOrCreateAccountAndServer } from '../account' 11import { getOrCreateAccountAndServer } from '../account'
12import { getOrCreateVideoChannel } from '../video-channels' 12import { getOrCreateVideoChannel } from '../video-channels'
13import { generateThumbnailFromUrl } from '../videos' 13import { generateThumbnailFromUrl } from '../videos'
14import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 14import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
15 15
16async function processAddActivity (activity: ActivityAdd) { 16async function processAddActivity (activity: ActivityAdd) {
17 const activityObject = activity.object 17 const activityObject = activity.object
@@ -37,12 +37,10 @@ export {
37 37
38// --------------------------------------------------------------------------- 38// ---------------------------------------------------------------------------
39 39
40async function processAddVideo ( 40async function processAddVideo (account: AccountInstance,
41 account: AccountInstance, 41 activity: ActivityAdd,
42 activity: ActivityAdd, 42 videoChannel: VideoChannelInstance,
43 videoChannel: VideoChannelInstance, 43 videoToCreateData: VideoTorrentObject) {
44 videoToCreateData: VideoTorrentObject
45) {
46 const options = { 44 const options = {
47 arguments: [ account, activity, videoChannel, videoToCreateData ], 45 arguments: [ account, activity, videoChannel, videoToCreateData ],
48 errorMessage: 'Cannot insert the remote video with many retries.' 46 errorMessage: 'Cannot insert the remote video with many retries.'
@@ -59,6 +57,10 @@ async function processAddVideo (
59 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike') 57 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
60 } 58 }
61 59
60 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
61 await addVideoShares(video, videoToCreateData.shares.orderedItems)
62 }
63
62 return video 64 return video
63} 65}
64 66
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index d8532d3a1..2aa9ad5c7 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -1,34 +1,23 @@
1import { ActivityAnnounce } from '../../../../shared/models/activitypub/activity' 1import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity'
2import { retryTransactionWrapper } from '../../../helpers/database-utils'
2import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
3import { database as db } from '../../../initializers/index' 4import { database as db } from '../../../initializers/index'
5import { AccountInstance } from '../../../models/account/account-interface'
4import { VideoInstance } from '../../../models/index' 6import { VideoInstance } from '../../../models/index'
5import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 7import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
8import { getOrCreateAccountAndServer } from '../account'
9import { forwardActivity } from '../send/misc'
6import { processAddActivity } from './process-add' 10import { processAddActivity } from './process-add'
7import { processCreateActivity } from './process-create' 11import { processCreateActivity } from './process-create'
8import { getOrCreateAccountAndServer } from '../account'
9 12
10async function processAnnounceActivity (activity: ActivityAnnounce) { 13async function processAnnounceActivity (activity: ActivityAnnounce) {
11 const announcedActivity = activity.object 14 const announcedActivity = activity.object
12 const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor) 15 const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor)
13 16
14 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') { 17 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
15 // Add share entry 18 return processVideoChannelShare(accountAnnouncer, activity)
16 const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
17 await db.VideoChannelShare.create({
18 accountId: accountAnnouncer.id,
19 videoChannelId: videoChannel.id
20 })
21
22 return undefined
23 } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') { 19 } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
24 // Add share entry 20 return processVideoShare(accountAnnouncer, activity)
25 const video: VideoInstance = await processAddActivity(announcedActivity)
26 await db.VideoShare.create({
27 accountId: accountAnnouncer.id,
28 videoId: video.id
29 })
30
31 return undefined
32 } 21 }
33 22
34 logger.warn( 23 logger.warn(
@@ -44,3 +33,78 @@ async function processAnnounceActivity (activity: ActivityAnnounce) {
44export { 33export {
45 processAnnounceActivity 34 processAnnounceActivity
46} 35}
36
37// ---------------------------------------------------------------------------
38
39function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
40 const options = {
41 arguments: [ accountAnnouncer, activity ],
42 errorMessage: 'Cannot share the video channel with many retries.'
43 }
44
45 return retryTransactionWrapper(shareVideoChannel, options)
46}
47
48async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
49 const announcedActivity = activity.object as ActivityCreate
50
51 return db.sequelize.transaction(async t => {
52 // Add share entry
53 const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
54 const share = {
55 accountId: accountAnnouncer.id,
56 videoChannelId: videoChannel.id
57 }
58
59 const [ , created ] = await db.VideoChannelShare.findOrCreate({
60 where: share,
61 defaults: share,
62 transaction: t
63 })
64
65 if (videoChannel.isOwned() && created === true) {
66 // Don't resend the activity to the sender
67 const exceptions = [ accountAnnouncer ]
68 await forwardActivity(activity, t, exceptions)
69 }
70
71 return undefined
72 })
73}
74
75function processVideoShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
76 const options = {
77 arguments: [ accountAnnouncer, activity ],
78 errorMessage: 'Cannot share the video with many retries.'
79 }
80
81 return retryTransactionWrapper(shareVideo, options)
82}
83
84function shareVideo (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
85 const announcedActivity = activity.object as ActivityAdd
86
87 return db.sequelize.transaction(async t => {
88 // Add share entry
89 const video: VideoInstance = await processAddActivity(announcedActivity)
90
91 const share = {
92 accountId: accountAnnouncer.id,
93 videoId: video.id
94 }
95
96 const [ , created ] = await db.VideoShare.findOrCreate({
97 where: share,
98 defaults: share,
99 transaction: t
100 })
101
102 if (video.isOwned() && created === true) {
103 // Don't resend the activity to the sender
104 const exceptions = [ accountAnnouncer ]
105 await forwardActivity(activity, t, exceptions)
106 }
107
108 return undefined
109 })
110}
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 1f982598b..c88082bbf 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -8,7 +8,7 @@ import { AccountInstance } from '../../../models/account/account-interface'
8import { getOrCreateAccountAndServer } from '../account' 8import { getOrCreateAccountAndServer } from '../account'
9import { forwardActivity } from '../send/misc' 9import { forwardActivity } from '../send/misc'
10import { getVideoChannelActivityPubUrl } from '../url' 10import { getVideoChannelActivityPubUrl } from '../url'
11import { videoChannelActivityObjectToDBAttributes } from './misc' 11import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc'
12 12
13async function processCreateActivity (activity: ActivityCreate) { 13async function processCreateActivity (activity: ActivityCreate) {
14 const activityObject = activity.object 14 const activityObject = activity.object
@@ -92,13 +92,19 @@ async function processCreateView (byAccount: AccountInstance, activity: Activity
92 } 92 }
93} 93}
94 94
95function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 95async function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
96 const options = { 96 const options = {
97 arguments: [ account, videoChannelToCreateData ], 97 arguments: [ account, videoChannelToCreateData ],
98 errorMessage: 'Cannot insert the remote video channel with many retries.' 98 errorMessage: 'Cannot insert the remote video channel with many retries.'
99 } 99 }
100 100
101 return retryTransactionWrapper(addRemoteVideoChannel, options) 101 const videoChannel = await retryTransactionWrapper(addRemoteVideoChannel, options)
102
103 if (videoChannelToCreateData.shares && Array.isArray(videoChannelToCreateData.shares.orderedItems)) {
104 await addVideoChannelShares(videoChannel, videoChannelToCreateData.shares.orderedItems)
105 }
106
107 return videoChannel
102} 108}
103 109
104function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 110function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index 444c1cbd6..fd1add68e 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -1,13 +1,14 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { Activity } from '../../../../shared/models/activitypub/activity'
2import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
3import { ACTIVITY_PUB, database as db } from '../../../initializers' 4import { ACTIVITY_PUB, database as db } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface' 5import { AccountInstance } from '../../../models/account/account-interface'
6import { VideoChannelInstance } from '../../../models/index'
7import { VideoInstance } from '../../../models/video/video-interface'
5import { 8import {
6 activitypubHttpJobScheduler, 9 activitypubHttpJobScheduler,
7 ActivityPubHttpPayload 10 ActivityPubHttpPayload
8} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' 11} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
9import { VideoInstance } from '../../../models/video/video-interface'
10import { Activity } from '../../../../shared/models/activitypub/activity'
11 12
12async function forwardActivity ( 13async function forwardActivity (
13 activity: Activity, 14 activity: Activity,
@@ -85,9 +86,16 @@ function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo:
85 } 86 }
86} 87}
87 88
88function getVideoFollowersAudience (accountsInvolvedInVideo: AccountInstance[]) { 89function getOriginVideoChannelAudience (videoChannel: VideoChannelInstance, accountsInvolved: AccountInstance[]) {
90 return {
91 to: [ videoChannel.Account.url ],
92 cc: accountsInvolved.map(a => a.followersUrl)
93 }
94}
95
96function getObjectFollowersAudience (accountsInvolvedInObject: AccountInstance[]) {
89 return { 97 return {
90 to: accountsInvolvedInVideo.map(a => a.followersUrl), 98 to: accountsInvolvedInObject.map(a => a.followersUrl),
91 cc: [] 99 cc: []
92 } 100 }
93} 101}
@@ -99,6 +107,13 @@ async function getAccountsInvolvedInVideo (video: VideoInstance) {
99 return accountsToForwardView 107 return accountsToForwardView
100} 108}
101 109
110async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelInstance) {
111 const accountsToForwardView = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
112 accountsToForwardView.push(videoChannel.Account)
113
114 return accountsToForwardView
115}
116
102async function getAudience (accountSender: AccountInstance, isPublic = true) { 117async function getAudience (accountSender: AccountInstance, isPublic = true) {
103 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls() 118 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
104 119
@@ -131,10 +146,12 @@ async function computeFollowerUris (toAccountFollower: AccountInstance[], follow
131 146
132export { 147export {
133 broadcastToFollowers, 148 broadcastToFollowers,
149 getOriginVideoChannelAudience,
134 unicastTo, 150 unicastTo,
135 getAudience, 151 getAudience,
136 getOriginVideoAudience, 152 getOriginVideoAudience,
137 getAccountsInvolvedInVideo, 153 getAccountsInvolvedInVideo,
138 getVideoFollowersAudience, 154 getAccountsInvolvedInVideoChannel,
155 getObjectFollowersAudience,
139 forwardActivity 156 forwardActivity
140} 157}
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index b8ea51bc0..efc23af46 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,34 +1,96 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/index' 2import { ActivityAdd } from '../../../../shared/index'
3import { ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' 3import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity'
4import { AccountInstance, VideoInstance } from '../../../models' 4import { AccountInstance, VideoInstance } from '../../../models'
5import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 5import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
6import { getAnnounceActivityPubUrl } from '../url' 6import { getAnnounceActivityPubUrl } from '../url'
7import { broadcastToFollowers } from './misc' 7import {
8 broadcastToFollowers,
9 getAccountsInvolvedInVideo,
10 getAccountsInvolvedInVideoChannel,
11 getAudience,
12 getObjectFollowersAudience,
13 getOriginVideoAudience,
14 getOriginVideoChannelAudience,
15 unicastTo
16} from './misc'
8import { addActivityData } from './send-add' 17import { addActivityData } from './send-add'
9import { createActivityData } from './send-create' 18import { createActivityData } from './send-create'
10 19
11async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 20async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
12 const url = getAnnounceActivityPubUrl(video.url, byAccount) 21 const url = getAnnounceActivityPubUrl(video.url, byAccount)
13 22
14 const videoChannel = video.VideoChannel 23 const videoChannel = video.VideoChannel
15 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject()) 24 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
16 25
17 const data = await announceActivityData(url, byAccount, announcedActivity) 26 const accountsToForwardView = await getAccountsInvolvedInVideo(video)
27 const audience = getObjectFollowersAudience(accountsToForwardView)
28 const data = await announceActivityData(url, byAccount, announcedActivity, audience)
29
30 return data
31}
32
33async function sendVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
34 const data = await buildVideoAnnounceToFollowers(byAccount, video, t)
35
18 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 36 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
19} 37}
20 38
21async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { 39async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
40 const url = getAnnounceActivityPubUrl(video.url, byAccount)
41
42 const videoChannel = video.VideoChannel
43 const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
44
45 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
46 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
47 const data = await createActivityData(url, byAccount, announcedActivity, audience)
48
49 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
50}
51
52async function buildVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
22 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) 53 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
23 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject()) 54 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
24 55
25 const data = await announceActivityData(url, byAccount, announcedActivity) 56 const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel)
57 const audience = getObjectFollowersAudience(accountsToForwardView)
58 const data = await announceActivityData(url, byAccount, announcedActivity, audience)
59
60 return data
61}
62
63async function sendVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
64 const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t)
65
26 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 66 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
27} 67}
28 68
29async function announceActivityData (url: string, byAccount: AccountInstance, object: ActivityCreate | ActivityAdd) { 69async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
70 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
71 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
72
73 const accountsInvolvedInVideo = await getAccountsInvolvedInVideoChannel(videoChannel)
74 const audience = getOriginVideoChannelAudience(videoChannel, accountsInvolvedInVideo)
75 const data = await createActivityData(url, byAccount, announcedActivity, audience)
76
77 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
78}
79
80async function announceActivityData (
81 url: string,
82 byAccount: AccountInstance,
83 object: ActivityCreate | ActivityAdd,
84 audience?: ActivityAudience
85) {
86 if (!audience) {
87 audience = await getAudience(byAccount)
88 }
89
30 const activity: ActivityAnnounce = { 90 const activity: ActivityAnnounce = {
31 type: 'Announce', 91 type: 'Announce',
92 to: audience.to,
93 cc: audience.cc,
32 id: url, 94 id: url,
33 actor: byAccount.url, 95 actor: byAccount.url,
34 object 96 object
@@ -40,7 +102,11 @@ async function announceActivityData (url: string, byAccount: AccountInstance, ob
40// --------------------------------------------------------------------------- 102// ---------------------------------------------------------------------------
41 103
42export { 104export {
43 sendVideoAnnounce, 105 sendVideoAnnounceToFollowers,
44 sendVideoChannelAnnounce, 106 sendVideoChannelAnnounceToFollowers,
45 announceActivityData 107 sendVideoAnnounceToOrigin,
108 sendVideoChannelAnnounceToOrigin,
109 announceActivityData,
110 buildVideoAnnounceToFollowers,
111 buildVideoChannelAnnounceToFollowers
46} 112}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 113d89233..bf66606c1 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -9,7 +9,7 @@ import {
9 getAccountsInvolvedInVideo, 9 getAccountsInvolvedInVideo,
10 getAudience, 10 getAudience,
11 getOriginVideoAudience, 11 getOriginVideoAudience,
12 getVideoFollowersAudience, 12 getObjectFollowersAudience,
13 unicastTo 13 unicastTo
14} from './misc' 14} from './misc'
15 15
@@ -47,7 +47,7 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
47 const viewActivity = createViewActivityData(byAccount, video) 47 const viewActivity = createViewActivityData(byAccount, video)
48 48
49 const accountsToForwardView = await getAccountsInvolvedInVideo(video) 49 const accountsToForwardView = await getAccountsInvolvedInVideo(video)
50 const audience = getVideoFollowersAudience(accountsToForwardView) 50 const audience = getObjectFollowersAudience(accountsToForwardView)
51 const data = await createActivityData(url, byAccount, viewActivity, audience) 51 const data = await createActivityData(url, byAccount, viewActivity, audience)
52 52
53 // Use the server account to send the view, because it could be an unregistered account 53 // Use the server account to send the view, because it could be an unregistered account
@@ -72,7 +72,7 @@ async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, vi
72 const dislikeActivity = createDislikeActivityData(byAccount, video) 72 const dislikeActivity = createDislikeActivityData(byAccount, video)
73 73
74 const accountsToForwardView = await getAccountsInvolvedInVideo(video) 74 const accountsToForwardView = await getAccountsInvolvedInVideo(video)
75 const audience = getVideoFollowersAudience(accountsToForwardView) 75 const audience = getObjectFollowersAudience(accountsToForwardView)
76 const data = await createActivityData(url, byAccount, dislikeActivity, audience) 76 const data = await createActivityData(url, byAccount, dislikeActivity, audience)
77 77
78 const followersException = [ byAccount ] 78 const followersException = [ byAccount ]
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 8ca775bf3..41b879b8a 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -7,7 +7,7 @@ import {
7 getAccountsInvolvedInVideo, 7 getAccountsInvolvedInVideo,
8 getAudience, 8 getAudience,
9 getOriginVideoAudience, 9 getOriginVideoAudience,
10 getVideoFollowersAudience, 10 getObjectFollowersAudience,
11 unicastTo 11 unicastTo
12} from './misc' 12} from './misc'
13 13
@@ -25,7 +25,7 @@ async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: Vide
25 const url = getVideoLikeActivityPubUrl(byAccount, video) 25 const url = getVideoLikeActivityPubUrl(byAccount, video)
26 26
27 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video) 27 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
28 const audience = getVideoFollowersAudience(accountsInvolvedInVideo) 28 const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
29 const data = await likeActivityData(url, byAccount, video, audience) 29 const data = await likeActivityData(url, byAccount, video, audience)
30 30
31 const toAccountsFollowers = await getAccountsInvolvedInVideo(video) 31 const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 79fc113f0..9b732df40 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -10,7 +10,7 @@ import { AccountInstance } from '../../../models'
10import { AccountFollowInstance } from '../../../models/account/account-follow-interface' 10import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
11import { VideoInstance } from '../../../models/video/video-interface' 11import { VideoInstance } from '../../../models/video/video-interface'
12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
13import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getVideoFollowersAudience, unicastTo } from './misc' 13import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getObjectFollowersAudience, unicastTo } from './misc'
14import { createActivityData, createDislikeActivityData } from './send-create' 14import { createActivityData, createDislikeActivityData } from './send-create'
15import { followActivityData } from './send-follow' 15import { followActivityData } from './send-follow'
16import { likeActivityData } from './send-like' 16import { likeActivityData } from './send-like'
@@ -43,7 +43,7 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video:
43 const undoUrl = getUndoActivityPubUrl(likeUrl) 43 const undoUrl = getUndoActivityPubUrl(likeUrl)
44 44
45 const toAccountsFollowers = await getAccountsInvolvedInVideo(video) 45 const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
46 const audience = getVideoFollowersAudience(toAccountsFollowers) 46 const audience = getObjectFollowersAudience(toAccountsFollowers)
47 const object = await likeActivityData(likeUrl, byAccount, video) 47 const object = await likeActivityData(likeUrl, byAccount, video)
48 const data = await undoActivityData(undoUrl, byAccount, object, audience) 48 const data = await undoActivityData(undoUrl, byAccount, object, audience)
49 49
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index 689e200a6..e14b0f50c 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -3,7 +3,7 @@ import { getServerAccount } from '../../helpers/utils'
3import { database as db } from '../../initializers' 3import { database as db } from '../../initializers'
4import { VideoChannelInstance } from '../../models/index' 4import { VideoChannelInstance } from '../../models/index'
5import { VideoInstance } from '../../models/video/video-interface' 5import { VideoInstance } from '../../models/video/video-interface'
6import { sendVideoAnnounce, sendVideoChannelAnnounce } from './send/send-announce' 6import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send/send-announce'
7 7
8async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) { 8async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) {
9 const serverAccount = await getServerAccount() 9 const serverAccount = await getServerAccount()
@@ -13,7 +13,7 @@ async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t:
13 videoChannelId: videoChannel.id 13 videoChannelId: videoChannel.id
14 }, { transaction: t }) 14 }, { transaction: t })
15 15
16 return sendVideoChannelAnnounce(serverAccount, videoChannel, t) 16 return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t)
17} 17}
18 18
19async function shareVideoByServer (video: VideoInstance, t: Transaction) { 19async function shareVideoByServer (video: VideoInstance, t: Transaction) {
@@ -24,7 +24,7 @@ async function shareVideoByServer (video: VideoInstance, t: Transaction) {
24 videoId: video.id 24 videoId: video.id
25 }, { transaction: t }) 25 }, { transaction: t })
26 26
27 return sendVideoAnnounce(serverAccount, video, t) 27 return sendVideoAnnounceToFollowers(serverAccount, video, t)
28} 28}
29 29
30export { 30export {
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index 17395a99b..6475c4218 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -22,37 +22,37 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
22} 22}
23 23
24function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 24function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
25 return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString() 25 return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString()
26} 26}
27 27
28function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 28function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
29 return byAccount.url + '#likes/' + video.id 29 return byAccount.url + '/likes/' + video.id
30} 30}
31 31
32function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 32function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
33 return byAccount.url + '#dislikes/' + video.id 33 return byAccount.url + '/dislikes/' + video.id
34} 34}
35 35
36function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { 36function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
37 const me = accountFollow.AccountFollower 37 const me = accountFollow.AccountFollower
38 const following = accountFollow.AccountFollowing 38 const following = accountFollow.AccountFollowing
39 39
40 return me.url + '#follows/' + following.id 40 return me.url + '/follows/' + following.id
41} 41}
42 42
43function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { 43function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) {
44 const follower = accountFollow.AccountFollower 44 const follower = accountFollow.AccountFollower
45 const me = accountFollow.AccountFollowing 45 const me = accountFollow.AccountFollowing
46 46
47 return follower.url + '#accepts/follows/' + me.id 47 return follower.url + '/accepts/follows/' + me.id
48} 48}
49 49
50function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { 50function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) {
51 return originalUrl + '#announces/' + byAccount.id 51 return originalUrl + '/announces/' + byAccount.id
52} 52}
53 53
54function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { 54function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
55 return originalUrl + '#updates/' + updatedAt 55 return originalUrl + '/updates/' + updatedAt
56} 56}
57 57
58function getUndoActivityPubUrl (originalUrl: string) { 58function getUndoActivityPubUrl (originalUrl: string) {
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
index 29ebd169d..9692f9be7 100644
--- a/server/middlewares/async.ts
+++ b/server/middlewares/async.ts
@@ -1,10 +1,18 @@
1import { Request, Response, NextFunction } from 'express' 1import { Request, Response, NextFunction, RequestHandler } from 'express'
2import { eachSeries } from 'async'
2 3
3// Syntactic sugar to avoid try/catch in express controllers 4// Syntactic sugar to avoid try/catch in express controllers
4// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 5// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
5function asyncMiddleware (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) { 6function asyncMiddleware (fun: RequestHandler | RequestHandler[]) {
6 return (req: Request, res: Response, next: NextFunction) => { 7 return (req: Request, res: Response, next: NextFunction) => {
7 return Promise.resolve(fn(req, res, next)) 8 if (Array.isArray(fun) === true) {
9 return eachSeries(fun as RequestHandler[], (f, cb) => {
10 Promise.resolve(f(req, res, cb))
11 .catch(next)
12 }, next)
13 }
14
15 return Promise.resolve((fun as RequestHandler)(req, res, next))
8 .catch(next) 16 .catch(next)
9 } 17 }
10} 18}
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts
index 07ae76b63..47ed6a7bb 100644
--- a/server/middlewares/validators/account.ts
+++ b/server/middlewares/validators/account.ts
@@ -1,9 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator/check' 2import { param } from 'express-validator/check'
3import { logger } from '../../helpers' 3import { logger } from '../../helpers'
4import { isAccountNameValid } from '../../helpers/custom-validators/accounts' 4import { checkLocalAccountNameExists, isAccountNameValid } from '../../helpers/custom-validators/accounts'
5import { database as db } from '../../initializers/database'
6import { AccountInstance } from '../../models'
7import { checkErrors } from './utils' 5import { checkErrors } from './utils'
8 6
9const localAccountValidator = [ 7const localAccountValidator = [
@@ -13,7 +11,7 @@ const localAccountValidator = [
13 logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) 11 logger.debug('Checking localAccountValidator parameters', { parameters: req.params })
14 12
15 checkErrors(req, res, () => { 13 checkErrors(req, res, () => {
16 checkLocalAccountExists(req.params.name, res, next) 14 checkLocalAccountNameExists(req.params.name, res, next)
17 }) 15 })
18 } 16 }
19] 17]
@@ -23,23 +21,3 @@ const localAccountValidator = [
23export { 21export {
24 localAccountValidator 22 localAccountValidator
25} 23}
26
27// ---------------------------------------------------------------------------
28
29function checkLocalAccountExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
30 db.Account.loadLocalByName(name)
31 .then(account => {
32 if (!account) {
33 return res.status(404)
34 .send({ error: 'Account not found' })
35 .end()
36 }
37
38 res.locals.account = account
39 return callback(null, account)
40 })
41 .catch(err => {
42 logger.error('Error in account request validator.', err)
43 return res.sendStatus(500)
44 })
45}
diff --git a/server/middlewares/validators/utils.ts b/server/middlewares/validators/utils.ts
index ea107bbe8..77a1a0d4b 100644
--- a/server/middlewares/validators/utils.ts
+++ b/server/middlewares/validators/utils.ts
@@ -14,8 +14,22 @@ function checkErrors (req: express.Request, res: express.Response, next: express
14 return next() 14 return next()
15} 15}
16 16
17function areValidationErrors (req: express.Request, res: express.Response) {
18 const errors = validationResult(req)
19
20 if (!errors.isEmpty()) {
21 logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() })
22 res.status(400).json({ errors: errors.mapped() })
23
24 return true
25 }
26
27 return false
28}
29
17// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
18 31
19export { 32export {
20 checkErrors 33 checkErrors,
34 areValidationErrors
21} 35}
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts
index c6fd3b59d..f30fbf0dc 100644
--- a/server/middlewares/validators/video-channels.ts
+++ b/server/middlewares/validators/video-channels.ts
@@ -1,13 +1,19 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator/check' 2import { body, param } from 'express-validator/check'
3import { UserRight } from '../../../shared' 3import { UserRight } from '../../../shared'
4import { checkVideoAccountExists } from '../../helpers/custom-validators/accounts' 4import { checkAccountIdExists } from '../../helpers/custom-validators/accounts'
5import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' 5import { isIdValid } from '../../helpers/custom-validators/misc'
6import { checkVideoChannelExists, isIdOrUUIDValid } from '../../helpers/index' 6import {
7 checkVideoChannelExists,
8 isVideoChannelDescriptionValid,
9 isVideoChannelExistsPromise,
10 isVideoChannelNameValid
11} from '../../helpers/custom-validators/video-channels'
12import { isIdOrUUIDValid } from '../../helpers/index'
7import { logger } from '../../helpers/logger' 13import { logger } from '../../helpers/logger'
8import { database as db } from '../../initializers' 14import { database as db } from '../../initializers'
9import { UserInstance } from '../../models' 15import { UserInstance } from '../../models'
10import { checkErrors } from './utils' 16import { areValidationErrors, checkErrors } from './utils'
11 17
12const listVideoAccountChannelsValidator = [ 18const listVideoAccountChannelsValidator = [
13 param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), 19 param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
@@ -16,7 +22,7 @@ const listVideoAccountChannelsValidator = [
16 logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) 22 logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body })
17 23
18 checkErrors(req, res, () => { 24 checkErrors(req, res, () => {
19 checkVideoAccountExists(req.params.accountId, res, next) 25 checkAccountIdExists(req.params.accountId, res, next)
20 }) 26 })
21 } 27 }
22] 28]
@@ -90,6 +96,28 @@ const videoChannelsGetValidator = [
90 } 96 }
91] 97]
92 98
99const videoChannelsShareValidator = [
100 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
101 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
102
103 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
104 logger.debug('Checking videoChannelShare parameters', { parameters: req.params })
105
106 if (areValidationErrors(req, res)) return
107 if (!await isVideoChannelExistsPromise(req.params.id, res)) return
108
109 const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId)
110 if (!share) {
111 return res.status(404)
112 .end()
113 }
114
115 res.locals.videoChannelShare = share
116
117 return next()
118 }
119]
120
93// --------------------------------------------------------------------------- 121// ---------------------------------------------------------------------------
94 122
95export { 123export {
@@ -97,7 +125,8 @@ export {
97 videoChannelsAddValidator, 125 videoChannelsAddValidator,
98 videoChannelsUpdateValidator, 126 videoChannelsUpdateValidator,
99 videoChannelsRemoveValidator, 127 videoChannelsRemoveValidator,
100 videoChannelsGetValidator 128 videoChannelsGetValidator,
129 videoChannelsShareValidator
101} 130}
102 131
103// --------------------------------------------------------------------------- 132// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index df0eb7b96..5ffc85210 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -24,7 +24,8 @@ import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers'
24import { database as db } from '../../initializers/database' 24import { database as db } from '../../initializers/database'
25import { UserInstance } from '../../models/account/user-interface' 25import { UserInstance } from '../../models/account/user-interface'
26import { authenticate } from '../oauth' 26import { authenticate } from '../oauth'
27import { checkErrors } from './utils' 27import { areValidationErrors, checkErrors } from './utils'
28import { isVideoExistsPromise } from '../../helpers/index'
28 29
29const videosAddValidator = [ 30const videosAddValidator = [
30 body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( 31 body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
@@ -230,6 +231,28 @@ const videoRateValidator = [
230 } 231 }
231] 232]
232 233
234const videosShareValidator = [
235 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
236 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
237
238 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
239 logger.debug('Checking videoShare parameters', { parameters: req.params })
240
241 if (areValidationErrors(req, res)) return
242 if (!await isVideoExistsPromise(req.params.id, res)) return
243
244 const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id)
245 if (!share) {
246 return res.status(404)
247 .end()
248 }
249
250 res.locals.videoShare = share
251
252 return next()
253 }
254]
255
233// --------------------------------------------------------------------------- 256// ---------------------------------------------------------------------------
234 257
235export { 258export {
@@ -238,6 +261,7 @@ export {
238 videosGetValidator, 261 videosGetValidator,
239 videosRemoveValidator, 262 videosRemoveValidator,
240 videosSearchValidator, 263 videosSearchValidator,
264 videosShareValidator,
241 265
242 videoAbuseReportValidator, 266 videoAbuseReportValidator,
243 267
diff --git a/server/models/video/video-channel-interface.ts b/server/models/video/video-channel-interface.ts
index b409d1db3..21f81e901 100644
--- a/server/models/video/video-channel-interface.ts
+++ b/server/models/video/video-channel-interface.ts
@@ -6,6 +6,7 @@ import { VideoChannelObject } from '../../../shared/models/activitypub/objects/v
6import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' 6import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model'
7import { AccountInstance } from '../account/account-interface' 7import { AccountInstance } from '../account/account-interface'
8import { VideoInstance } from './video-interface' 8import { VideoInstance } from './video-interface'
9import { VideoChannelShareInstance } from './video-channel-share-interface'
9 10
10export namespace VideoChannelMethods { 11export namespace VideoChannelMethods {
11 export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel 12 export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel
@@ -47,6 +48,7 @@ export interface VideoChannelAttributes {
47 48
48 Account?: AccountInstance 49 Account?: AccountInstance
49 Videos?: VideoInstance[] 50 Videos?: VideoInstance[]
51 VideoChannelShares?: VideoChannelShareInstance[]
50} 52}
51 53
52export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> { 54export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> {
diff --git a/server/models/video/video-channel-share-interface.ts b/server/models/video/video-channel-share-interface.ts
index 8bb531af2..bcb3a0e24 100644
--- a/server/models/video/video-channel-share-interface.ts
+++ b/server/models/video/video-channel-share-interface.ts
@@ -5,10 +5,12 @@ import { VideoChannelInstance } from './video-channel-interface'
5 5
6export namespace VideoChannelShareMethods { 6export namespace VideoChannelShareMethods {
7 export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]> 7 export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]>
8 export type Load = (accountId: number, videoId: number) => Bluebird<VideoChannelShareInstance>
8} 9}
9 10
10export interface VideoChannelShareClass { 11export interface VideoChannelShareClass {
11 loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare 12 loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
13 load: VideoChannelShareMethods.Load
12} 14}
13 15
14export interface VideoChannelShareAttributes { 16export interface VideoChannelShareAttributes {
diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts
index 01f84c806..e47c0dae7 100644
--- a/server/models/video/video-channel-share.ts
+++ b/server/models/video/video-channel-share.ts
@@ -5,6 +5,7 @@ import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelSha
5 5
6let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> 6let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes>
7let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare 7let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
8let load: VideoChannelShareMethods.Load
8 9
9export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
10 VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare', 11 VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare',
@@ -23,6 +24,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
23 24
24 const classMethods = [ 25 const classMethods = [
25 associate, 26 associate,
27 load,
26 loadAccountsByShare 28 loadAccountsByShare
27 ] 29 ]
28 addMethodsToModel(VideoChannelShare, classMethods) 30 addMethodsToModel(VideoChannelShare, classMethods)
@@ -50,6 +52,19 @@ function associate (models) {
50 }) 52 })
51} 53}
52 54
55load = function (accountId: number, videoChannelId: number) {
56 return VideoChannelShare.findOne({
57 where: {
58 accountId,
59 videoChannelId
60 },
61 include: [
62 VideoChannelShare['sequelize'].models.Account,
63 VideoChannelShare['sequelize'].models.VideoChannel
64 ]
65 })
66}
67
53loadAccountsByShare = function (videoChannelId: number) { 68loadAccountsByShare = function (videoChannelId: number) {
54 const query = { 69 const query = {
55 where: { 70 where: {
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 64130310d..e11268b2c 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -6,6 +6,8 @@ import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete'
6 6
7import { addMethodsToModel, getSort } from '../utils' 7import { addMethodsToModel, getSort } from '../utils'
8import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface' 8import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface'
9import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
10import { activityPubCollection } from '../../helpers/activitypub'
9 11
10let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> 12let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
11let toFormattedJSON: VideoChannelMethods.ToFormattedJSON 13let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
@@ -139,6 +141,18 @@ toFormattedJSON = function (this: VideoChannelInstance) {
139} 141}
140 142
141toActivityPubObject = function (this: VideoChannelInstance) { 143toActivityPubObject = function (this: VideoChannelInstance) {
144 let sharesObject
145 if (Array.isArray(this.VideoChannelShares)) {
146 const shares: string[] = []
147
148 for (const videoChannelShare of this.VideoChannelShares) {
149 const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account)
150 shares.push(shareUrl)
151 }
152
153 sharesObject = activityPubCollection(shares)
154 }
155
142 const json = { 156 const json = {
143 type: 'VideoChannel' as 'VideoChannel', 157 type: 'VideoChannel' as 'VideoChannel',
144 id: this.url, 158 id: this.url,
@@ -146,7 +160,8 @@ toActivityPubObject = function (this: VideoChannelInstance) {
146 content: this.description, 160 content: this.description,
147 name: this.name, 161 name: this.name,
148 published: this.createdAt.toISOString(), 162 published: this.createdAt.toISOString(),
149 updated: this.updatedAt.toISOString() 163 updated: this.updatedAt.toISOString(),
164 shares: sharesObject
150 } 165 }
151 166
152 return json 167 return json
diff --git a/server/models/video/video-share-interface.ts b/server/models/video/video-share-interface.ts
index 569568842..ad23444b6 100644
--- a/server/models/video/video-share-interface.ts
+++ b/server/models/video/video-share-interface.ts
@@ -1,14 +1,16 @@
1import * as Bluebird from 'bluebird'
1import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
2import { AccountInstance } from '../account/account-interface' 3import { AccountInstance } from '../account/account-interface'
3import { VideoInstance } from './video-interface' 4import { VideoInstance } from './video-interface'
4import * as Bluebird from 'bluebird'
5 5
6export namespace VideoShareMethods { 6export namespace VideoShareMethods {
7 export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]> 7 export type LoadAccountsByShare = (videoId: number) => Bluebird<AccountInstance[]>
8 export type Load = (accountId: number, videoId: number) => Bluebird<VideoShareInstance>
8} 9}
9 10
10export interface VideoShareClass { 11export interface VideoShareClass {
11 loadAccountsByShare: VideoShareMethods.LoadAccountsByShare 12 loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
13 load: VideoShareMethods.Load
12} 14}
13 15
14export interface VideoShareAttributes { 16export interface VideoShareAttributes {
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 22ac31a4a..fe5d56d42 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -5,6 +5,7 @@ import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './v
5 5
6let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes> 6let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes>
7let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare 7let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
8let load: VideoShareMethods.Load
8 9
9export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
10 VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare', 11 VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare',
@@ -23,7 +24,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
23 24
24 const classMethods = [ 25 const classMethods = [
25 associate, 26 associate,
26 loadAccountsByShare 27 loadAccountsByShare,
28 load
27 ] 29 ]
28 addMethodsToModel(VideoShare, classMethods) 30 addMethodsToModel(VideoShare, classMethods)
29 31
@@ -50,6 +52,18 @@ function associate (models) {
50 }) 52 })
51} 53}
52 54
55load = function (accountId: number, videoId: number) {
56 return VideoShare.findOne({
57 where: {
58 accountId,
59 videoId
60 },
61 include: [
62 VideoShare['sequelize'].models.Account
63 ]
64 })
65}
66
53loadAccountsByShare = function (videoId: number) { 67loadAccountsByShare = function (videoId: number) {
54 const query = { 68 const query = {
55 where: { 69 where: {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 457bfce77..4956b57ee 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -32,6 +32,7 @@ import { isVideoNameValid, isVideoLicenceValid, isVideoNSFWValid, isVideoDescrip
32import { logger } from '../../helpers/logger' 32import { logger } from '../../helpers/logger'
33import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils' 33import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils'
34import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils' 34import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils'
35import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
35 36
36let Video: Sequelize.Model<VideoInstance, VideoAttributes> 37let Video: Sequelize.Model<VideoInstance, VideoAttributes>
37let getOriginalFile: VideoMethods.GetOriginalFile 38let getOriginalFile: VideoMethods.GetOriginalFile
@@ -573,6 +574,18 @@ toActivityPubObject = function (this: VideoInstance) {
573 dislikesObject = activityPubCollection(dislikes) 574 dislikesObject = activityPubCollection(dislikes)
574 } 575 }
575 576
577 let sharesObject
578 if (Array.isArray(this.VideoShares)) {
579 const shares: string[] = []
580
581 for (const videoShare of this.VideoShares) {
582 const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account)
583 shares.push(shareUrl)
584 }
585
586 sharesObject = activityPubCollection(shares)
587 }
588
576 const url = [] 589 const url = []
577 for (const file of this.VideoFiles) { 590 for (const file of this.VideoFiles) {
578 url.push({ 591 url.push({
@@ -630,7 +643,8 @@ toActivityPubObject = function (this: VideoInstance) {
630 }, 643 },
631 url, 644 url,
632 likes: likesObject, 645 likes: likesObject,
633 dislikes: dislikesObject 646 dislikes: dislikesObject,
647 shares: sharesObject
634 } 648 }
635 649
636 return videoObject 650 return videoObject
@@ -823,7 +837,8 @@ listAllAndSharedByAccountForOutbox = function (accountId: number, start: number,
823 accountId 837 accountId
824 } 838 }
825 ] 839 ]
826 } 840 },
841 include: [ Video['sequelize'].models.Account ]
827 }, 842 },
828 { 843 {
829 model: Video['sequelize'].models.VideoChannel, 844 model: Video['sequelize'].models.VideoChannel,