import { pageToStartAndCount } from '../../helpers'
import { AccountInstance, VideoChannelInstance } from '../../models'
import { activityPubCollectionPagination } from '../../helpers/activitypub'
-import { ACTIVITY_PUB } from '../../initializers/constants'
+import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants'
import { asyncMiddleware } from '../../middlewares/async'
import { videosGetValidator } from '../../middlewares/validators/videos'
import { VideoInstance } from '../../models/video/video-interface'
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count)
- const activityPubResult = activityPubCollectionPagination(req.url, page, result)
+ const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
return res.json(activityPubResult)
}
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count)
- const activityPubResult = activityPubCollectionPagination(req.url, page, result)
+ const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
return res.json(activityPubResult)
}
import * as express from 'express'
import { activityPubClientRouter } from './client'
import { inboxRouter } from './inbox'
+import { outboxRouter } from './outbox'
const activityPubRouter = express.Router()
activityPubRouter.use('/', inboxRouter)
+activityPubRouter.use('/', outboxRouter)
activityPubRouter.use('/', activityPubClientRouter)
// ---------------------------------------------------------------------------
--- /dev/null
+import * as express from 'express'
+import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity'
+import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
+import { database as db } from '../../initializers'
+import { addActivityData } from '../../lib/activitypub/send/send-add'
+import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
+import { announceActivityData } from '../../lib/index'
+import { asyncMiddleware, localAccountValidator } from '../../middlewares'
+import { AccountInstance } from '../../models/account/account-interface'
+import { pageToStartAndCount } from '../../helpers/core-utils'
+import { ACTIVITY_PUB } from '../../initializers/constants'
+
+const outboxRouter = express.Router()
+
+outboxRouter.get('/account/:name/outbox',
+ localAccountValidator,
+ asyncMiddleware(outboxController)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+ outboxRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const account: AccountInstance = res.locals.account
+
+ const page = req.params.page || 1
+ const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
+
+ const data = await db.Video.listAllAndSharedByAccountForOutbox(account.id, start, count)
+ const activities: Activity[] = []
+
+ console.log(account.url)
+
+ for (const video of data.data) {
+ const videoObject = video.toActivityPubObject()
+ let addActivity: ActivityAdd = await addActivityData(video.url, account, video, video.VideoChannel.url, videoObject)
+
+ // This is a shared video
+ if (video.VideoShare !== undefined) {
+ const url = getAnnounceActivityPubUrl(video.url, account)
+ const announceActivity = await announceActivityData(url, account, addActivity)
+ activities.push(announceActivity)
+ } else {
+ activities.push(addActivity)
+ }
+ }
+
+ const newResult = {
+ data: activities,
+ total: data.total
+ }
+ const json = activityPubCollectionPagination(account.url + '/outbox', page, newResult)
+
+ return res.json(json).end()
+}
import { ResultList } from '../../shared/models/result-list.model'
import { AccountInstance } from '../models/account/account-interface'
import { signObject } from './peertube-crypto'
+import { ACTIVITY_PUB } from '../initializers/constants'
function activityPubContextify <T> (data: T) {
return Object.assign(data,{
}
function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) {
- const baseUrl = url.split('?').shift
+ let next: string
+ let prev: string
+
+ // There are more results
+ if (result.total > ((page + 1) * ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)) {
+ next = url + '?page=' + (page + 1)
+ }
+
+ if (page > 1) {
+ prev = url + '?page=' + (page - 1)
+ }
+
+ const orderedCollectionPagination = {
+ id: url + '?page=' + page,
+ type: 'OrderedCollectionPage',
+ prev,
+ next,
+ partOf: url,
+ orderedItems: result.data
+ }
const obj = {
- id: baseUrl,
- type: 'Collection',
+ id: url,
+ type: 'OrderedCollection',
totalItems: result.total,
- first: {
- id: baseUrl + '?page=' + page,
- type: 'CollectionPage',
- totalItems: result.total,
- next: baseUrl + '?page=' + (page + 1),
- partOf: baseUrl,
- items: result.data
- }
+ orderedItems: orderedCollectionPagination
}
return activityPubContextify(obj)
import { Transaction } from 'sequelize'
+import { ActivityAdd } from '../../../../shared/index'
+import { ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity'
import { AccountInstance, VideoInstance } from '../../../models'
import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { getAnnounceActivityPubUrl } from '../url'
import { broadcastToFollowers } from './misc'
import { addActivityData } from './send-add'
import { createActivityData } from './send-create'
-import { getAnnounceActivityPubUrl } from '../url'
async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
const url = getAnnounceActivityPubUrl(video.url, byAccount)
return broadcastToFollowers(data, byAccount, [ byAccount ], t)
}
-// ---------------------------------------------------------------------------
-
-export {
- sendVideoAnnounce,
- sendVideoChannelAnnounce
-}
-
-// ---------------------------------------------------------------------------
-
-async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
- const activity = {
+async function announceActivityData (url: string, byAccount: AccountInstance, object: ActivityCreate | ActivityAdd) {
+ const activity: ActivityAnnounce = {
type: 'Announce',
id: url,
actor: byAccount.url,
return activity
}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendVideoAnnounce,
+ sendVideoChannelAnnounce,
+ announceActivityData
+}
'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
- if (start !== undefined) query += 'LIMIT ' + start
- if (count !== undefined) query += ', ' + count
+ if (count !== undefined) query += 'LIMIT ' + count
+ if (start !== undefined) query += ' OFFSET ' + start
const options = {
bind: { accountIds },
import { TagAttributes, TagInstance } from './tag-interface'
import { VideoChannelInstance } from './video-channel-interface'
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
+import { VideoShareInstance } from './video-share-interface'
export namespace VideoMethods {
export type GetThumbnailName = (this: VideoInstance) => string
export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]>
export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]>
+ export type ListAllAndSharedByAccountForOutbox = (
+ accountId: number,
+ start: number,
+ count: number
+ ) => Bluebird< ResultList<VideoInstance> >
export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
export type SearchAndPopulateAccountAndServerAndTags = (
export interface VideoClass {
generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
list: VideoMethods.List
+ listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
listForApi: VideoMethods.ListForApi
listUserVideosForApi: VideoMethods.ListUserVideosForApi
listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
VideoChannel?: VideoChannelInstance
Tags?: TagInstance[]
VideoFiles?: VideoFileInstance[]
+ VideoShare?: VideoShareInstance
}
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
let list: VideoMethods.List
let listForApi: VideoMethods.ListForApi
+let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
let listUserVideosForApi: VideoMethods.ListUserVideosForApi
let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
generateThumbnailFromData,
list,
+ listAllAndSharedByAccountForOutbox,
listForApi,
listUserVideosForApi,
listOwnedAndPopulateAccountAndTags,
},
onDelete: 'cascade'
})
+
+ Video.hasMany(models.VideoShare, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
}
function afterDestroy (video: VideoInstance) {
return Video.findAll(query)
}
+listAllAndSharedByAccountForOutbox = function (accountId: number, start: number, count: number) {
+ const queryVideo = 'SELECT "Video"."id" FROM "Videos" AS "Video" ' +
+ 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
+ 'WHERE "VideoChannel"."accountId" = ' + accountId
+ const queryVideoShare = 'SELECT "Video"."id" FROM "VideoShares" AS "VideoShare" ' +
+ 'INNER JOIN "Videos" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
+ 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
+ 'WHERE "VideoShare"."accountId" = ' + accountId
+ const rawQuery = `(${queryVideo}) UNION (${queryVideoShare}) LIMIT ${count} OFFSET ${start}`
+
+ const query = {
+ distinct: true,
+ offset: start,
+ limit: count,
+ order: [ getSort('createdAt'), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
+ where: {
+ id: {
+ [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')')
+ }
+ },
+ include: [
+ {
+ model: Video['sequelize'].models.VideoShare,
+ required: false
+ },
+ {
+ model: Video['sequelize'].models.VideoChannel,
+ required: true,
+ include: [
+ {
+ model: Video['sequelize'].models.Account,
+ required: true
+ }
+ ]
+ },
+ Video['sequelize'].models.Tag,
+ Video['sequelize'].models.VideoFile
+ ]
+ }
+
+ return Video.findAndCountAll(query).then(({ rows, count }) => {
+ return {
+ data: rows,
+ total: count
+ }
+ })
+}
+
listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) {
const query = {
distinct: true,