aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-21 18:23:10 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:53 +0100
commite71bcc0f4b31ecfd84a786411febfc6d18a85258 (patch)
treeea31776b6bc69bd3b72e6c6f615cf94072271c82 /server
parentb1cbc0dd3ee0fce6d8390b6d3996386a5b6097ac (diff)
downloadPeerTube-e71bcc0f4b31ecfd84a786411febfc6d18a85258.tar.gz
PeerTube-e71bcc0f4b31ecfd84a786411febfc6d18a85258.tar.zst
PeerTube-e71bcc0f4b31ecfd84a786411febfc6d18a85258.zip
Add outbox
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts6
-rw-r--r--server/controllers/activitypub/index.ts2
-rw-r--r--server/controllers/activitypub/outbox.ts60
-rw-r--r--server/helpers/activitypub.ts35
-rw-r--r--server/lib/activitypub/send/send-announce.ts25
-rw-r--r--server/models/account/account-follow.ts4
-rw-r--r--server/models/video/video-interface.ts8
-rw-r--r--server/models/video/video.ts58
8 files changed, 170 insertions, 28 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 7b3921770..24c8665a5 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -6,7 +6,7 @@ import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
6import { pageToStartAndCount } from '../../helpers' 6import { pageToStartAndCount } from '../../helpers'
7import { AccountInstance, VideoChannelInstance } from '../../models' 7import { AccountInstance, VideoChannelInstance } from '../../models'
8import { activityPubCollectionPagination } from '../../helpers/activitypub' 8import { activityPubCollectionPagination } from '../../helpers/activitypub'
9import { ACTIVITY_PUB } from '../../initializers/constants' 9import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants'
10import { asyncMiddleware } from '../../middlewares/async' 10import { asyncMiddleware } from '../../middlewares/async'
11import { videosGetValidator } from '../../middlewares/validators/videos' 11import { videosGetValidator } from '../../middlewares/validators/videos'
12import { VideoInstance } from '../../models/video/video-interface' 12import { VideoInstance } from '../../models/video/video-interface'
@@ -60,7 +60,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
60 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 60 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
61 61
62 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count) 62 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count)
63 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 63 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
64 64
65 return res.json(activityPubResult) 65 return res.json(activityPubResult)
66} 66}
@@ -72,7 +72,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
72 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 72 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
73 73
74 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count) 74 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count)
75 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 75 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
76 76
77 return res.json(activityPubResult) 77 return res.json(activityPubResult)
78} 78}
diff --git a/server/controllers/activitypub/index.ts b/server/controllers/activitypub/index.ts
index c5bec6448..7e81902af 100644
--- a/server/controllers/activitypub/index.ts
+++ b/server/controllers/activitypub/index.ts
@@ -1,10 +1,12 @@
1import * as express from 'express' 1import * as express from 'express'
2import { activityPubClientRouter } from './client' 2import { activityPubClientRouter } from './client'
3import { inboxRouter } from './inbox' 3import { inboxRouter } from './inbox'
4import { outboxRouter } from './outbox'
4 5
5const activityPubRouter = express.Router() 6const activityPubRouter = express.Router()
6 7
7activityPubRouter.use('/', inboxRouter) 8activityPubRouter.use('/', inboxRouter)
9activityPubRouter.use('/', outboxRouter)
8activityPubRouter.use('/', activityPubClientRouter) 10activityPubRouter.use('/', activityPubClientRouter)
9 11
10// --------------------------------------------------------------------------- 12// ---------------------------------------------------------------------------
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
new file mode 100644
index 000000000..396fa2db5
--- /dev/null
+++ b/server/controllers/activitypub/outbox.ts
@@ -0,0 +1,60 @@
1import * as express from 'express'
2import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity'
3import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
4import { database as db } from '../../initializers'
5import { addActivityData } from '../../lib/activitypub/send/send-add'
6import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
7import { announceActivityData } from '../../lib/index'
8import { asyncMiddleware, localAccountValidator } from '../../middlewares'
9import { AccountInstance } from '../../models/account/account-interface'
10import { pageToStartAndCount } from '../../helpers/core-utils'
11import { ACTIVITY_PUB } from '../../initializers/constants'
12
13const outboxRouter = express.Router()
14
15outboxRouter.get('/account/:name/outbox',
16 localAccountValidator,
17 asyncMiddleware(outboxController)
18)
19
20// ---------------------------------------------------------------------------
21
22export {
23 outboxRouter
24}
25
26// ---------------------------------------------------------------------------
27
28async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
29 const account: AccountInstance = res.locals.account
30
31 const page = req.params.page || 1
32 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
33
34 const data = await db.Video.listAllAndSharedByAccountForOutbox(account.id, start, count)
35 const activities: Activity[] = []
36
37 console.log(account.url)
38
39 for (const video of data.data) {
40 const videoObject = video.toActivityPubObject()
41 let addActivity: ActivityAdd = await addActivityData(video.url, account, video, video.VideoChannel.url, videoObject)
42
43 // This is a shared video
44 if (video.VideoShare !== undefined) {
45 const url = getAnnounceActivityPubUrl(video.url, account)
46 const announceActivity = await announceActivityData(url, account, addActivity)
47 activities.push(announceActivity)
48 } else {
49 activities.push(addActivity)
50 }
51 }
52
53 const newResult = {
54 data: activities,
55 total: data.total
56 }
57 const json = activityPubCollectionPagination(account.url + '/outbox', page, newResult)
58
59 return res.json(json).end()
60}
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 5c577bb61..04d85b8e6 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -2,6 +2,7 @@ import { Activity } from '../../shared/models/activitypub/activity'
2import { ResultList } from '../../shared/models/result-list.model' 2import { ResultList } from '../../shared/models/result-list.model'
3import { AccountInstance } from '../models/account/account-interface' 3import { AccountInstance } from '../models/account/account-interface'
4import { signObject } from './peertube-crypto' 4import { signObject } from './peertube-crypto'
5import { ACTIVITY_PUB } from '../initializers/constants'
5 6
6function activityPubContextify <T> (data: T) { 7function activityPubContextify <T> (data: T) {
7 return Object.assign(data,{ 8 return Object.assign(data,{
@@ -24,20 +25,32 @@ function activityPubContextify <T> (data: T) {
24} 25}
25 26
26function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) { 27function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) {
27 const baseUrl = url.split('?').shift 28 let next: string
29 let prev: string
30
31 // There are more results
32 if (result.total > ((page + 1) * ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)) {
33 next = url + '?page=' + (page + 1)
34 }
35
36 if (page > 1) {
37 prev = url + '?page=' + (page - 1)
38 }
39
40 const orderedCollectionPagination = {
41 id: url + '?page=' + page,
42 type: 'OrderedCollectionPage',
43 prev,
44 next,
45 partOf: url,
46 orderedItems: result.data
47 }
28 48
29 const obj = { 49 const obj = {
30 id: baseUrl, 50 id: url,
31 type: 'Collection', 51 type: 'OrderedCollection',
32 totalItems: result.total, 52 totalItems: result.total,
33 first: { 53 orderedItems: orderedCollectionPagination
34 id: baseUrl + '?page=' + page,
35 type: 'CollectionPage',
36 totalItems: result.total,
37 next: baseUrl + '?page=' + (page + 1),
38 partOf: baseUrl,
39 items: result.data
40 }
41 } 54 }
42 55
43 return activityPubContextify(obj) 56 return activityPubContextify(obj)
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 4b3a4ef75..b8ea51bc0 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,10 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/index'
3import { ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity'
2import { AccountInstance, VideoInstance } from '../../../models' 4import { AccountInstance, VideoInstance } from '../../../models'
3import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 5import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
6import { getAnnounceActivityPubUrl } from '../url'
4import { broadcastToFollowers } from './misc' 7import { broadcastToFollowers } from './misc'
5import { addActivityData } from './send-add' 8import { addActivityData } from './send-add'
6import { createActivityData } from './send-create' 9import { createActivityData } from './send-create'
7import { getAnnounceActivityPubUrl } from '../url'
8 10
9async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 11async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
10 const url = getAnnounceActivityPubUrl(video.url, byAccount) 12 const url = getAnnounceActivityPubUrl(video.url, byAccount)
@@ -24,17 +26,8 @@ async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChanne
24 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 26 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
25} 27}
26 28
27// --------------------------------------------------------------------------- 29async function announceActivityData (url: string, byAccount: AccountInstance, object: ActivityCreate | ActivityAdd) {
28 30 const activity: ActivityAnnounce = {
29export {
30 sendVideoAnnounce,
31 sendVideoChannelAnnounce
32}
33
34// ---------------------------------------------------------------------------
35
36async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
37 const activity = {
38 type: 'Announce', 31 type: 'Announce',
39 id: url, 32 id: url,
40 actor: byAccount.url, 33 actor: byAccount.url,
@@ -43,3 +36,11 @@ async function announceActivityData (url: string, byAccount: AccountInstance, ob
43 36
44 return activity 37 return activity
45} 38}
39
40// ---------------------------------------------------------------------------
41
42export {
43 sendVideoAnnounce,
44 sendVideoChannelAnnounce,
45 announceActivityData
46}
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index 34ba3f8db..578bcda39 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -221,8 +221,8 @@ async function createListAcceptedFollowForApiQuery (
221 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + 221 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
222 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' ' 222 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
223 223
224 if (start !== undefined) query += 'LIMIT ' + start 224 if (count !== undefined) query += 'LIMIT ' + count
225 if (count !== undefined) query += ', ' + count 225 if (start !== undefined) query += ' OFFSET ' + start
226 226
227 const options = { 227 const options = {
228 bind: { accountIds }, 228 bind: { accountIds },
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 9f29c842c..391ecff43 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -7,6 +7,7 @@ import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from '
7import { TagAttributes, TagInstance } from './tag-interface' 7import { TagAttributes, TagInstance } from './tag-interface'
8import { VideoChannelInstance } from './video-channel-interface' 8import { VideoChannelInstance } from './video-channel-interface'
9import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' 9import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
10import { VideoShareInstance } from './video-share-interface'
10 11
11export namespace VideoMethods { 12export namespace VideoMethods {
12 export type GetThumbnailName = (this: VideoInstance) => string 13 export type GetThumbnailName = (this: VideoInstance) => string
@@ -44,6 +45,11 @@ export namespace VideoMethods {
44 export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]> 45 export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]>
45 export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]> 46 export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]>
46 47
48 export type ListAllAndSharedByAccountForOutbox = (
49 accountId: number,
50 start: number,
51 count: number
52 ) => Bluebird< ResultList<VideoInstance> >
47 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > 53 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
48 export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > 54 export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
49 export type SearchAndPopulateAccountAndServerAndTags = ( 55 export type SearchAndPopulateAccountAndServerAndTags = (
@@ -73,6 +79,7 @@ export namespace VideoMethods {
73export interface VideoClass { 79export interface VideoClass {
74 generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData 80 generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
75 list: VideoMethods.List 81 list: VideoMethods.List
82 listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
76 listForApi: VideoMethods.ListForApi 83 listForApi: VideoMethods.ListForApi
77 listUserVideosForApi: VideoMethods.ListUserVideosForApi 84 listUserVideosForApi: VideoMethods.ListUserVideosForApi
78 listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags 85 listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
@@ -115,6 +122,7 @@ export interface VideoAttributes {
115 VideoChannel?: VideoChannelInstance 122 VideoChannel?: VideoChannelInstance
116 Tags?: TagInstance[] 123 Tags?: TagInstance[]
117 VideoFiles?: VideoFileInstance[] 124 VideoFiles?: VideoFileInstance[]
125 VideoShare?: VideoShareInstance
118} 126}
119 127
120export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { 128export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e2069eb0c..3b7e83779 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -78,6 +78,7 @@ let getLanguageLabel: VideoMethods.GetLanguageLabel
78let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData 78let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
79let list: VideoMethods.List 79let list: VideoMethods.List
80let listForApi: VideoMethods.ListForApi 80let listForApi: VideoMethods.ListForApi
81let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
81let listUserVideosForApi: VideoMethods.ListUserVideosForApi 82let listUserVideosForApi: VideoMethods.ListUserVideosForApi
82let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID 83let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
83let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags 84let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
@@ -266,6 +267,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
266 267
267 generateThumbnailFromData, 268 generateThumbnailFromData,
268 list, 269 list,
270 listAllAndSharedByAccountForOutbox,
269 listForApi, 271 listForApi,
270 listUserVideosForApi, 272 listUserVideosForApi,
271 listOwnedAndPopulateAccountAndTags, 273 listOwnedAndPopulateAccountAndTags,
@@ -348,6 +350,14 @@ function associate (models) {
348 }, 350 },
349 onDelete: 'cascade' 351 onDelete: 'cascade'
350 }) 352 })
353
354 Video.hasMany(models.VideoShare, {
355 foreignKey: {
356 name: 'videoId',
357 allowNull: false
358 },
359 onDelete: 'cascade'
360 })
351} 361}
352 362
353function afterDestroy (video: VideoInstance) { 363function afterDestroy (video: VideoInstance) {
@@ -775,6 +785,54 @@ list = function () {
775 return Video.findAll(query) 785 return Video.findAll(query)
776} 786}
777 787
788listAllAndSharedByAccountForOutbox = function (accountId: number, start: number, count: number) {
789 const queryVideo = 'SELECT "Video"."id" FROM "Videos" AS "Video" ' +
790 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
791 'WHERE "VideoChannel"."accountId" = ' + accountId
792 const queryVideoShare = 'SELECT "Video"."id" FROM "VideoShares" AS "VideoShare" ' +
793 'INNER JOIN "Videos" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
794 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
795 'WHERE "VideoShare"."accountId" = ' + accountId
796 const rawQuery = `(${queryVideo}) UNION (${queryVideoShare}) LIMIT ${count} OFFSET ${start}`
797
798 const query = {
799 distinct: true,
800 offset: start,
801 limit: count,
802 order: [ getSort('createdAt'), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
803 where: {
804 id: {
805 [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')')
806 }
807 },
808 include: [
809 {
810 model: Video['sequelize'].models.VideoShare,
811 required: false
812 },
813 {
814 model: Video['sequelize'].models.VideoChannel,
815 required: true,
816 include: [
817 {
818 model: Video['sequelize'].models.Account,
819 required: true
820 }
821 ]
822 },
823 Video['sequelize'].models.Tag,
824 Video['sequelize'].models.VideoFile
825 ]
826 }
827
828 return Video.findAndCountAll(query).then(({ rows, count }) => {
829 return {
830 data: rows,
831 total: count
832 }
833 })
834}
835
778listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { 836listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) {
779 const query = { 837 const query = {
780 distinct: true, 838 distinct: true,