Add shares forward and collection on videos/video channels

This commit is contained in:
Chocobozzz 2017-11-27 14:44:51 +01:00
parent 74bb2cb834
commit 4e50b6a1c9
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
29 changed files with 546 additions and 133 deletions

View File

@ -1,22 +1,26 @@
// Intercept ActivityPub client requests // Intercept ActivityPub client requests
import * as express from 'express' import * as express from 'express'
import { pageToStartAndCount } from '../../helpers'
import { activityPubCollectionPagination } from '../../helpers/activitypub'
import { database as db } from '../../initializers' import { database as db } from '../../initializers'
import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
import { pageToStartAndCount } from '../../helpers'
import { AccountInstance, VideoChannelInstance } from '../../models'
import { activityPubCollectionPagination } from '../../helpers/activitypub'
import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants' import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants'
import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send/send-announce'
import { buildVideoAnnounceToFollowers } from '../../lib/index'
import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
import { asyncMiddleware } from '../../middlewares/async' import { asyncMiddleware } from '../../middlewares/async'
import { videosGetValidator } from '../../middlewares/validators/videos' import { videoChannelsGetValidator, videoChannelsShareValidator } from '../../middlewares/validators/video-channels'
import { videosGetValidator, videosShareValidator } from '../../middlewares/validators/videos'
import { AccountInstance, VideoChannelInstance } from '../../models'
import { VideoChannelShareInstance } from '../../models/video/video-channel-share-interface'
import { VideoInstance } from '../../models/video/video-interface' import { VideoInstance } from '../../models/video/video-interface'
import { videoChannelsGetValidator } from '../../middlewares/validators/video-channels' import { VideoShareInstance } from '../../models/video/video-share-interface'
const activityPubClientRouter = express.Router() const activityPubClientRouter = express.Router()
activityPubClientRouter.get('/account/:name', activityPubClientRouter.get('/account/:name',
executeIfActivityPub(localAccountValidator), executeIfActivityPub(localAccountValidator),
executeIfActivityPub(asyncMiddleware(accountController)) executeIfActivityPub(accountController)
) )
activityPubClientRouter.get('/account/:name/followers', activityPubClientRouter.get('/account/:name/followers',
@ -31,7 +35,12 @@ activityPubClientRouter.get('/account/:name/following',
activityPubClientRouter.get('/videos/watch/:id', activityPubClientRouter.get('/videos/watch/:id',
executeIfActivityPub(videosGetValidator), executeIfActivityPub(videosGetValidator),
executeIfActivityPub(asyncMiddleware(videoController)) executeIfActivityPub(videoController)
)
activityPubClientRouter.get('/videos/watch/:id/announces/:accountId',
executeIfActivityPub(asyncMiddleware(videosShareValidator)),
executeIfActivityPub(asyncMiddleware(videoAnnounceController))
) )
activityPubClientRouter.get('/video-channels/:id', activityPubClientRouter.get('/video-channels/:id',
@ -39,6 +48,11 @@ activityPubClientRouter.get('/video-channels/:id',
executeIfActivityPub(asyncMiddleware(videoChannelController)) executeIfActivityPub(asyncMiddleware(videoChannelController))
) )
activityPubClientRouter.get('/video-channels/:id/announces/:accountId',
executeIfActivityPub(asyncMiddleware(videoChannelsShareValidator)),
executeIfActivityPub(asyncMiddleware(videoChannelAnnounceController))
)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
@ -47,7 +61,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { function accountController (req: express.Request, res: express.Response, next: express.NextFunction) {
const account: AccountInstance = res.locals.account const account: AccountInstance = res.locals.account
return res.json(account.toActivityPubObject()).end() return res.json(account.toActivityPubObject()).end()
@ -77,12 +91,26 @@ async function accountFollowingController (req: express.Request, res: express.Re
return res.json(activityPubResult) return res.json(activityPubResult)
} }
async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
const video: VideoInstance = res.locals.video const video: VideoInstance = res.locals.video
return res.json(video.toActivityPubObject()) return res.json(video.toActivityPubObject())
} }
async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
const share = res.locals.videoShare as VideoShareInstance
const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined)
return res.json(object)
}
async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
const share = res.locals.videoChannelShare as VideoChannelShareInstance
const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined)
return res.json(object)
}
async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) { async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoChannel: VideoChannelInstance = res.locals.videoChannel const videoChannel: VideoChannelInstance = res.locals.videoChannel

View File

@ -1,4 +1,4 @@
import * as Promise from 'bluebird' import * as Bluebird from 'bluebird'
import * as express from 'express' import * as express from 'express'
import 'express-validator' import 'express-validator'
import * as validator from 'validator' import * as validator from 'validator'
@ -11,33 +11,45 @@ function isAccountNameValid (value: string) {
return isUserUsernameValid(value) return isUserUsernameValid(value)
} }
function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { function checkAccountIdExists (id: number | string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
let promise: Promise<AccountInstance> let promise: Bluebird<AccountInstance>
if (validator.isInt(id)) {
if (validator.isInt('' + id)) {
promise = db.Account.load(+id) promise = db.Account.load(+id)
} else { // UUID } else { // UUID
promise = db.Account.loadByUUID(id) promise = db.Account.loadByUUID('' + id)
} }
promise.then(account => { return checkAccountExists(promise, res, callback)
}
function checkLocalAccountNameExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
const p = db.Account.loadLocalByName(name)
return checkAccountExists(p, res, callback)
}
function checkAccountExists (p: Bluebird<AccountInstance>, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
p.then(account => {
if (!account) { if (!account) {
return res.status(404) return res.status(404)
.json({ error: 'Video account not found' }) .send({ error: 'Account not found' })
.end() .end()
} }
res.locals.account = account res.locals.account = account
callback() return callback(null, account)
})
.catch(err => {
logger.error('Error in video account request validator.', err)
return res.sendStatus(500)
}) })
.catch(err => {
logger.error('Error in account request validator.', err)
return res.sendStatus(500)
})
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
checkVideoAccountExists, checkAccountIdExists,
checkLocalAccountNameExists,
isAccountNameValid isAccountNameValid
} }

View File

@ -1,14 +1,14 @@
import * as Promise from 'bluebird' import * as Bluebird from 'bluebird'
import * as validator from 'validator'
import * as express from 'express' import * as express from 'express'
import 'express-validator' import 'express-validator'
import 'multer' import 'multer'
import * as validator from 'validator'
import { database as db, CONSTRAINTS_FIELDS } from '../../initializers' import { CONSTRAINTS_FIELDS, database as db } from '../../initializers'
import { VideoChannelInstance } from '../../models' import { VideoChannelInstance } from '../../models'
import { logger } from '../logger' import { logger } from '../logger'
import { exists } from './misc'
import { isActivityPubUrlValid } from './index' import { isActivityPubUrlValid } from './index'
import { exists } from './misc'
const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
@ -25,7 +25,7 @@ function isVideoChannelNameValid (value: string) {
} }
function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) { function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
let promise: Promise<VideoChannelInstance> let promise: Bluebird<VideoChannelInstance>
if (validator.isInt(id)) { if (validator.isInt(id)) {
promise = db.VideoChannel.loadAndPopulateAccount(+id) promise = db.VideoChannel.loadAndPopulateAccount(+id)
} else { // UUID } else { // UUID
@ -48,11 +48,32 @@ function checkVideoChannelExists (id: string, res: express.Response, callback: (
}) })
} }
async function isVideoChannelExistsPromise (id: string, res: express.Response) {
let videoChannel: VideoChannelInstance
if (validator.isInt(id)) {
videoChannel = await db.VideoChannel.loadAndPopulateAccount(+id)
} else { // UUID
videoChannel = await db.VideoChannel.loadByUUIDAndPopulateAccount(id)
}
if (!videoChannel) {
res.status(404)
.json({ error: 'Video channel not found' })
.end()
return false
}
res.locals.videoChannel = videoChannel
return true
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
isVideoChannelDescriptionValid, isVideoChannelDescriptionValid,
isVideoChannelNameValid,
checkVideoChannelExists, checkVideoChannelExists,
isVideoChannelNameValid,
isVideoChannelExistsPromise,
isVideoChannelUrlValid isVideoChannelUrlValid
} }

View File

@ -130,6 +130,27 @@ function checkVideoExists (id: string, res: Response, callback: () => void) {
}) })
} }
async function isVideoExistsPromise (id: string, res: Response) {
let video: VideoInstance
if (validator.isInt(id)) {
video = await db.Video.loadAndPopulateAccountAndServerAndTags(+id)
} else { // UUID
video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id)
}
if (!video) {
res.status(404)
.json({ error: 'Video not found' })
.end()
return false
}
res.locals.video = video
return true
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
@ -152,5 +173,6 @@ export {
isVideoPrivacyValid, isVideoPrivacyValid,
isVideoFileResolutionValid, isVideoFileResolutionValid,
isVideoFileSizeValid, isVideoFileSizeValid,
checkVideoExists checkVideoExists,
isVideoExistsPromise
} }

View File

@ -1,13 +1,16 @@
import * as magnetUtil from 'magnet-uri' import * as magnetUtil from 'magnet-uri'
import { VideoTorrentObject } from '../../../../shared' import { VideoTorrentObject } from '../../../../shared'
import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object' import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object'
import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
import { doRequest } from '../../../helpers/requests'
import { database as db } from '../../../initializers'
import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants' import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants'
import { AccountInstance } from '../../../models/account/account-interface' import { AccountInstance } from '../../../models/account/account-interface'
import { VideoChannelInstance } from '../../../models/video/video-channel-interface' import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
import { VideoFileAttributes } from '../../../models/video/video-file-interface' import { VideoFileAttributes } from '../../../models/video/video-file-interface'
import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface' import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface'
import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' import { getOrCreateAccountAndServer } from '../account'
function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) {
return { return {
@ -97,10 +100,60 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO
return attributes return attributes
} }
async function addVideoShares (instance: VideoInstance, shares: string[]) {
for (const share of shares) {
// Fetch url
const json = await doRequest({
uri: share,
json: true
})
const actor = json['actor']
if (!actor) continue
const account = await getOrCreateAccountAndServer(actor)
const entry = {
accountId: account.id,
videoId: instance.id
}
await db.VideoShare.findOrCreate({
where: entry,
defaults: entry
})
}
}
async function addVideoChannelShares (instance: VideoChannelInstance, shares: string[]) {
for (const share of shares) {
// Fetch url
const json = await doRequest({
uri: share,
json: true
})
const actor = json['actor']
if (!actor) continue
const account = await getOrCreateAccountAndServer(actor)
const entry = {
accountId: account.id,
videoChannelId: instance.id
}
await db.VideoChannelShare.findOrCreate({
where: entry,
defaults: entry
})
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
videoFileActivityUrlToDBAttributes, videoFileActivityUrlToDBAttributes,
videoActivityObjectToDBAttributes, videoActivityObjectToDBAttributes,
videoChannelActivityObjectToDBAttributes videoChannelActivityObjectToDBAttributes,
addVideoChannelShares,
addVideoShares
} }

View File

@ -11,7 +11,7 @@ import { VideoInstance } from '../../../models/video/video-interface'
import { getOrCreateAccountAndServer } from '../account' import { getOrCreateAccountAndServer } from '../account'
import { getOrCreateVideoChannel } from '../video-channels' import { getOrCreateVideoChannel } from '../video-channels'
import { generateThumbnailFromUrl } from '../videos' import { generateThumbnailFromUrl } from '../videos'
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
async function processAddActivity (activity: ActivityAdd) { async function processAddActivity (activity: ActivityAdd) {
const activityObject = activity.object const activityObject = activity.object
@ -37,12 +37,10 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processAddVideo ( async function processAddVideo (account: AccountInstance,
account: AccountInstance, activity: ActivityAdd,
activity: ActivityAdd, videoChannel: VideoChannelInstance,
videoChannel: VideoChannelInstance, videoToCreateData: VideoTorrentObject) {
videoToCreateData: VideoTorrentObject
) {
const options = { const options = {
arguments: [ account, activity, videoChannel, videoToCreateData ], arguments: [ account, activity, videoChannel, videoToCreateData ],
errorMessage: 'Cannot insert the remote video with many retries.' errorMessage: 'Cannot insert the remote video with many retries.'
@ -59,6 +57,10 @@ async function processAddVideo (
await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike') await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
} }
if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
await addVideoShares(video, videoToCreateData.shares.orderedItems)
}
return video return video
} }

View File

@ -1,34 +1,23 @@
import { ActivityAnnounce } from '../../../../shared/models/activitypub/activity' import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { database as db } from '../../../initializers/index' import { database as db } from '../../../initializers/index'
import { AccountInstance } from '../../../models/account/account-interface'
import { VideoInstance } from '../../../models/index' import { VideoInstance } from '../../../models/index'
import { VideoChannelInstance } from '../../../models/video/video-channel-interface' import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
import { getOrCreateAccountAndServer } from '../account'
import { forwardActivity } from '../send/misc'
import { processAddActivity } from './process-add' import { processAddActivity } from './process-add'
import { processCreateActivity } from './process-create' import { processCreateActivity } from './process-create'
import { getOrCreateAccountAndServer } from '../account'
async function processAnnounceActivity (activity: ActivityAnnounce) { async function processAnnounceActivity (activity: ActivityAnnounce) {
const announcedActivity = activity.object const announcedActivity = activity.object
const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor) const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor)
if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') { if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
// Add share entry return processVideoChannelShare(accountAnnouncer, activity)
const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
await db.VideoChannelShare.create({
accountId: accountAnnouncer.id,
videoChannelId: videoChannel.id
})
return undefined
} else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') { } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
// Add share entry return processVideoShare(accountAnnouncer, activity)
const video: VideoInstance = await processAddActivity(announcedActivity)
await db.VideoShare.create({
accountId: accountAnnouncer.id,
videoId: video.id
})
return undefined
} }
logger.warn( logger.warn(
@ -44,3 +33,78 @@ async function processAnnounceActivity (activity: ActivityAnnounce) {
export { export {
processAnnounceActivity processAnnounceActivity
} }
// ---------------------------------------------------------------------------
function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
const options = {
arguments: [ accountAnnouncer, activity ],
errorMessage: 'Cannot share the video channel with many retries.'
}
return retryTransactionWrapper(shareVideoChannel, options)
}
async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
const announcedActivity = activity.object as ActivityCreate
return db.sequelize.transaction(async t => {
// Add share entry
const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
const share = {
accountId: accountAnnouncer.id,
videoChannelId: videoChannel.id
}
const [ , created ] = await db.VideoChannelShare.findOrCreate({
where: share,
defaults: share,
transaction: t
})
if (videoChannel.isOwned() && created === true) {
// Don't resend the activity to the sender
const exceptions = [ accountAnnouncer ]
await forwardActivity(activity, t, exceptions)
}
return undefined
})
}
function processVideoShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
const options = {
arguments: [ accountAnnouncer, activity ],
errorMessage: 'Cannot share the video with many retries.'
}
return retryTransactionWrapper(shareVideo, options)
}
function shareVideo (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) {
const announcedActivity = activity.object as ActivityAdd
return db.sequelize.transaction(async t => {
// Add share entry
const video: VideoInstance = await processAddActivity(announcedActivity)
const share = {
accountId: accountAnnouncer.id,
videoId: video.id
}
const [ , created ] = await db.VideoShare.findOrCreate({
where: share,
defaults: share,
transaction: t
})
if (video.isOwned() && created === true) {
// Don't resend the activity to the sender
const exceptions = [ accountAnnouncer ]
await forwardActivity(activity, t, exceptions)
}
return undefined
})
}

View File

@ -8,7 +8,7 @@ import { AccountInstance } from '../../../models/account/account-interface'
import { getOrCreateAccountAndServer } from '../account' import { getOrCreateAccountAndServer } from '../account'
import { forwardActivity } from '../send/misc' import { forwardActivity } from '../send/misc'
import { getVideoChannelActivityPubUrl } from '../url' import { getVideoChannelActivityPubUrl } from '../url'
import { videoChannelActivityObjectToDBAttributes } from './misc' import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc'
async function processCreateActivity (activity: ActivityCreate) { async function processCreateActivity (activity: ActivityCreate) {
const activityObject = activity.object const activityObject = activity.object
@ -92,13 +92,19 @@ async function processCreateView (byAccount: AccountInstance, activity: Activity
} }
} }
function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { async function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
const options = { const options = {
arguments: [ account, videoChannelToCreateData ], arguments: [ account, videoChannelToCreateData ],
errorMessage: 'Cannot insert the remote video channel with many retries.' errorMessage: 'Cannot insert the remote video channel with many retries.'
} }
return retryTransactionWrapper(addRemoteVideoChannel, options) const videoChannel = await retryTransactionWrapper(addRemoteVideoChannel, options)
if (videoChannelToCreateData.shares && Array.isArray(videoChannelToCreateData.shares.orderedItems)) {
await addVideoChannelShares(videoChannel, videoChannelToCreateData.shares.orderedItems)
}
return videoChannel
} }
function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {

View File

@ -1,13 +1,14 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { Activity } from '../../../../shared/models/activitypub/activity'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { ACTIVITY_PUB, database as db } from '../../../initializers' import { ACTIVITY_PUB, database as db } from '../../../initializers'
import { AccountInstance } from '../../../models/account/account-interface' import { AccountInstance } from '../../../models/account/account-interface'
import { VideoChannelInstance } from '../../../models/index'
import { VideoInstance } from '../../../models/video/video-interface'
import { import {
activitypubHttpJobScheduler, activitypubHttpJobScheduler,
ActivityPubHttpPayload ActivityPubHttpPayload
} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
import { VideoInstance } from '../../../models/video/video-interface'
import { Activity } from '../../../../shared/models/activitypub/activity'
async function forwardActivity ( async function forwardActivity (
activity: Activity, activity: Activity,
@ -85,9 +86,16 @@ function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo:
} }
} }
function getVideoFollowersAudience (accountsInvolvedInVideo: AccountInstance[]) { function getOriginVideoChannelAudience (videoChannel: VideoChannelInstance, accountsInvolved: AccountInstance[]) {
return { return {
to: accountsInvolvedInVideo.map(a => a.followersUrl), to: [ videoChannel.Account.url ],
cc: accountsInvolved.map(a => a.followersUrl)
}
}
function getObjectFollowersAudience (accountsInvolvedInObject: AccountInstance[]) {
return {
to: accountsInvolvedInObject.map(a => a.followersUrl),
cc: [] cc: []
} }
} }
@ -99,6 +107,13 @@ async function getAccountsInvolvedInVideo (video: VideoInstance) {
return accountsToForwardView return accountsToForwardView
} }
async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelInstance) {
const accountsToForwardView = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
accountsToForwardView.push(videoChannel.Account)
return accountsToForwardView
}
async function getAudience (accountSender: AccountInstance, isPublic = true) { async function getAudience (accountSender: AccountInstance, isPublic = true) {
const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls() const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
@ -131,10 +146,12 @@ async function computeFollowerUris (toAccountFollower: AccountInstance[], follow
export { export {
broadcastToFollowers, broadcastToFollowers,
getOriginVideoChannelAudience,
unicastTo, unicastTo,
getAudience, getAudience,
getOriginVideoAudience, getOriginVideoAudience,
getAccountsInvolvedInVideo, getAccountsInvolvedInVideo,
getVideoFollowersAudience, getAccountsInvolvedInVideoChannel,
getObjectFollowersAudience,
forwardActivity forwardActivity
} }

View File

@ -1,34 +1,96 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { ActivityAdd } from '../../../../shared/index' import { ActivityAdd } from '../../../../shared/index'
import { ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity'
import { AccountInstance, VideoInstance } from '../../../models' import { AccountInstance, VideoInstance } from '../../../models'
import { VideoChannelInstance } from '../../../models/video/video-channel-interface' import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
import { getAnnounceActivityPubUrl } from '../url' import { getAnnounceActivityPubUrl } from '../url'
import { broadcastToFollowers } from './misc' import {
broadcastToFollowers,
getAccountsInvolvedInVideo,
getAccountsInvolvedInVideoChannel,
getAudience,
getObjectFollowersAudience,
getOriginVideoAudience,
getOriginVideoChannelAudience,
unicastTo
} from './misc'
import { addActivityData } from './send-add' import { addActivityData } from './send-add'
import { createActivityData } from './send-create' import { createActivityData } from './send-create'
async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
const url = getAnnounceActivityPubUrl(video.url, byAccount) const url = getAnnounceActivityPubUrl(video.url, byAccount)
const videoChannel = video.VideoChannel const videoChannel = video.VideoChannel
const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject()) const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
const data = await announceActivityData(url, byAccount, announcedActivity) const accountsToForwardView = await getAccountsInvolvedInVideo(video)
const audience = getObjectFollowersAudience(accountsToForwardView)
const data = await announceActivityData(url, byAccount, announcedActivity, audience)
return data
}
async function sendVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
const data = await buildVideoAnnounceToFollowers(byAccount, video, t)
return broadcastToFollowers(data, byAccount, [ byAccount ], t) return broadcastToFollowers(data, byAccount, [ byAccount ], t)
} }
async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
const url = getAnnounceActivityPubUrl(video.url, byAccount)
const videoChannel = video.VideoChannel
const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
const data = await createActivityData(url, byAccount, announcedActivity, audience)
return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
}
async function buildVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject()) const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
const data = await announceActivityData(url, byAccount, announcedActivity) const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel)
const audience = getObjectFollowersAudience(accountsToForwardView)
const data = await announceActivityData(url, byAccount, announcedActivity, audience)
return data
}
async function sendVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t)
return broadcastToFollowers(data, byAccount, [ byAccount ], t) return broadcastToFollowers(data, byAccount, [ byAccount ], t)
} }
async function announceActivityData (url: string, byAccount: AccountInstance, object: ActivityCreate | ActivityAdd) { async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
const accountsInvolvedInVideo = await getAccountsInvolvedInVideoChannel(videoChannel)
const audience = getOriginVideoChannelAudience(videoChannel, accountsInvolvedInVideo)
const data = await createActivityData(url, byAccount, announcedActivity, audience)
return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
}
async function announceActivityData (
url: string,
byAccount: AccountInstance,
object: ActivityCreate | ActivityAdd,
audience?: ActivityAudience
) {
if (!audience) {
audience = await getAudience(byAccount)
}
const activity: ActivityAnnounce = { const activity: ActivityAnnounce = {
type: 'Announce', type: 'Announce',
to: audience.to,
cc: audience.cc,
id: url, id: url,
actor: byAccount.url, actor: byAccount.url,
object object
@ -40,7 +102,11 @@ async function announceActivityData (url: string, byAccount: AccountInstance, ob
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
sendVideoAnnounce, sendVideoAnnounceToFollowers,
sendVideoChannelAnnounce, sendVideoChannelAnnounceToFollowers,
announceActivityData sendVideoAnnounceToOrigin,
sendVideoChannelAnnounceToOrigin,
announceActivityData,
buildVideoAnnounceToFollowers,
buildVideoChannelAnnounceToFollowers
} }

View File

@ -9,7 +9,7 @@ import {
getAccountsInvolvedInVideo, getAccountsInvolvedInVideo,
getAudience, getAudience,
getOriginVideoAudience, getOriginVideoAudience,
getVideoFollowersAudience, getObjectFollowersAudience,
unicastTo unicastTo
} from './misc' } from './misc'
@ -47,7 +47,7 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
const viewActivity = createViewActivityData(byAccount, video) const viewActivity = createViewActivityData(byAccount, video)
const accountsToForwardView = await getAccountsInvolvedInVideo(video) const accountsToForwardView = await getAccountsInvolvedInVideo(video)
const audience = getVideoFollowersAudience(accountsToForwardView) const audience = getObjectFollowersAudience(accountsToForwardView)
const data = await createActivityData(url, byAccount, viewActivity, audience) const data = await createActivityData(url, byAccount, viewActivity, audience)
// Use the server account to send the view, because it could be an unregistered account // 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
const dislikeActivity = createDislikeActivityData(byAccount, video) const dislikeActivity = createDislikeActivityData(byAccount, video)
const accountsToForwardView = await getAccountsInvolvedInVideo(video) const accountsToForwardView = await getAccountsInvolvedInVideo(video)
const audience = getVideoFollowersAudience(accountsToForwardView) const audience = getObjectFollowersAudience(accountsToForwardView)
const data = await createActivityData(url, byAccount, dislikeActivity, audience) const data = await createActivityData(url, byAccount, dislikeActivity, audience)
const followersException = [ byAccount ] const followersException = [ byAccount ]

View File

@ -7,7 +7,7 @@ import {
getAccountsInvolvedInVideo, getAccountsInvolvedInVideo,
getAudience, getAudience,
getOriginVideoAudience, getOriginVideoAudience,
getVideoFollowersAudience, getObjectFollowersAudience,
unicastTo unicastTo
} from './misc' } from './misc'
@ -25,7 +25,7 @@ async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: Vide
const url = getVideoLikeActivityPubUrl(byAccount, video) const url = getVideoLikeActivityPubUrl(byAccount, video)
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video) const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
const audience = getVideoFollowersAudience(accountsInvolvedInVideo) const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
const data = await likeActivityData(url, byAccount, video, audience) const data = await likeActivityData(url, byAccount, video, audience)
const toAccountsFollowers = await getAccountsInvolvedInVideo(video) const toAccountsFollowers = await getAccountsInvolvedInVideo(video)

View File

@ -10,7 +10,7 @@ import { AccountInstance } from '../../../models'
import { AccountFollowInstance } from '../../../models/account/account-follow-interface' import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
import { VideoInstance } from '../../../models/video/video-interface' import { VideoInstance } from '../../../models/video/video-interface'
import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getVideoFollowersAudience, unicastTo } from './misc' import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getObjectFollowersAudience, unicastTo } from './misc'
import { createActivityData, createDislikeActivityData } from './send-create' import { createActivityData, createDislikeActivityData } from './send-create'
import { followActivityData } from './send-follow' import { followActivityData } from './send-follow'
import { likeActivityData } from './send-like' import { likeActivityData } from './send-like'
@ -43,7 +43,7 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video:
const undoUrl = getUndoActivityPubUrl(likeUrl) const undoUrl = getUndoActivityPubUrl(likeUrl)
const toAccountsFollowers = await getAccountsInvolvedInVideo(video) const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
const audience = getVideoFollowersAudience(toAccountsFollowers) const audience = getObjectFollowersAudience(toAccountsFollowers)
const object = await likeActivityData(likeUrl, byAccount, video) const object = await likeActivityData(likeUrl, byAccount, video)
const data = await undoActivityData(undoUrl, byAccount, object, audience) const data = await undoActivityData(undoUrl, byAccount, object, audience)

View File

@ -3,7 +3,7 @@ import { getServerAccount } from '../../helpers/utils'
import { database as db } from '../../initializers' import { database as db } from '../../initializers'
import { VideoChannelInstance } from '../../models/index' import { VideoChannelInstance } from '../../models/index'
import { VideoInstance } from '../../models/video/video-interface' import { VideoInstance } from '../../models/video/video-interface'
import { sendVideoAnnounce, sendVideoChannelAnnounce } from './send/send-announce' import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send/send-announce'
async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) { async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) {
const serverAccount = await getServerAccount() const serverAccount = await getServerAccount()
@ -13,7 +13,7 @@ async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t:
videoChannelId: videoChannel.id videoChannelId: videoChannel.id
}, { transaction: t }) }, { transaction: t })
return sendVideoChannelAnnounce(serverAccount, videoChannel, t) return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t)
} }
async function shareVideoByServer (video: VideoInstance, t: Transaction) { async function shareVideoByServer (video: VideoInstance, t: Transaction) {
@ -24,7 +24,7 @@ async function shareVideoByServer (video: VideoInstance, t: Transaction) {
videoId: video.id videoId: video.id
}, { transaction: t }) }, { transaction: t })
return sendVideoAnnounce(serverAccount, video, t) return sendVideoAnnounceToFollowers(serverAccount, video, t)
} }
export { export {

View File

@ -22,37 +22,37 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
} }
function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString() return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString()
} }
function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
return byAccount.url + '#likes/' + video.id return byAccount.url + '/likes/' + video.id
} }
function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
return byAccount.url + '#dislikes/' + video.id return byAccount.url + '/dislikes/' + video.id
} }
function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
const me = accountFollow.AccountFollower const me = accountFollow.AccountFollower
const following = accountFollow.AccountFollowing const following = accountFollow.AccountFollowing
return me.url + '#follows/' + following.id return me.url + '/follows/' + following.id
} }
function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) {
const follower = accountFollow.AccountFollower const follower = accountFollow.AccountFollower
const me = accountFollow.AccountFollowing const me = accountFollow.AccountFollowing
return follower.url + '#accepts/follows/' + me.id return follower.url + '/accepts/follows/' + me.id
} }
function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) {
return originalUrl + '#announces/' + byAccount.id return originalUrl + '/announces/' + byAccount.id
} }
function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
return originalUrl + '#updates/' + updatedAt return originalUrl + '/updates/' + updatedAt
} }
function getUndoActivityPubUrl (originalUrl: string) { function getUndoActivityPubUrl (originalUrl: string) {

View File

@ -1,10 +1,18 @@
import { Request, Response, NextFunction } from 'express' import { Request, Response, NextFunction, RequestHandler } from 'express'
import { eachSeries } from 'async'
// Syntactic sugar to avoid try/catch in express controllers // Syntactic sugar to avoid try/catch in express controllers
// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 // Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
function asyncMiddleware (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) { function asyncMiddleware (fun: RequestHandler | RequestHandler[]) {
return (req: Request, res: Response, next: NextFunction) => { return (req: Request, res: Response, next: NextFunction) => {
return Promise.resolve(fn(req, res, next)) if (Array.isArray(fun) === true) {
return eachSeries(fun as RequestHandler[], (f, cb) => {
Promise.resolve(f(req, res, cb))
.catch(next)
}, next)
}
return Promise.resolve((fun as RequestHandler)(req, res, next))
.catch(next) .catch(next)
} }
} }

View File

@ -1,9 +1,7 @@
import * as express from 'express' import * as express from 'express'
import { param } from 'express-validator/check' import { param } from 'express-validator/check'
import { logger } from '../../helpers' import { logger } from '../../helpers'
import { isAccountNameValid } from '../../helpers/custom-validators/accounts' import { checkLocalAccountNameExists, isAccountNameValid } from '../../helpers/custom-validators/accounts'
import { database as db } from '../../initializers/database'
import { AccountInstance } from '../../models'
import { checkErrors } from './utils' import { checkErrors } from './utils'
const localAccountValidator = [ const localAccountValidator = [
@ -13,7 +11,7 @@ const localAccountValidator = [
logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) logger.debug('Checking localAccountValidator parameters', { parameters: req.params })
checkErrors(req, res, () => { checkErrors(req, res, () => {
checkLocalAccountExists(req.params.name, res, next) checkLocalAccountNameExists(req.params.name, res, next)
}) })
} }
] ]
@ -23,23 +21,3 @@ const localAccountValidator = [
export { export {
localAccountValidator localAccountValidator
} }
// ---------------------------------------------------------------------------
function checkLocalAccountExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
db.Account.loadLocalByName(name)
.then(account => {
if (!account) {
return res.status(404)
.send({ error: 'Account not found' })
.end()
}
res.locals.account = account
return callback(null, account)
})
.catch(err => {
logger.error('Error in account request validator.', err)
return res.sendStatus(500)
})
}

View File

@ -14,8 +14,22 @@ function checkErrors (req: express.Request, res: express.Response, next: express
return next() return next()
} }
function areValidationErrors (req: express.Request, res: express.Response) {
const errors = validationResult(req)
if (!errors.isEmpty()) {
logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() })
res.status(400).json({ errors: errors.mapped() })
return true
}
return false
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
checkErrors checkErrors,
areValidationErrors
} }

View File

@ -1,13 +1,19 @@
import * as express from 'express' import * as express from 'express'
import { body, param } from 'express-validator/check' import { body, param } from 'express-validator/check'
import { UserRight } from '../../../shared' import { UserRight } from '../../../shared'
import { checkVideoAccountExists } from '../../helpers/custom-validators/accounts' import { checkAccountIdExists } from '../../helpers/custom-validators/accounts'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' import { isIdValid } from '../../helpers/custom-validators/misc'
import { checkVideoChannelExists, isIdOrUUIDValid } from '../../helpers/index' import {
checkVideoChannelExists,
isVideoChannelDescriptionValid,
isVideoChannelExistsPromise,
isVideoChannelNameValid
} from '../../helpers/custom-validators/video-channels'
import { isIdOrUUIDValid } from '../../helpers/index'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { database as db } from '../../initializers' import { database as db } from '../../initializers'
import { UserInstance } from '../../models' import { UserInstance } from '../../models'
import { checkErrors } from './utils' import { areValidationErrors, checkErrors } from './utils'
const listVideoAccountChannelsValidator = [ const listVideoAccountChannelsValidator = [
param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
@ -16,7 +22,7 @@ const listVideoAccountChannelsValidator = [
logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body })
checkErrors(req, res, () => { checkErrors(req, res, () => {
checkVideoAccountExists(req.params.accountId, res, next) checkAccountIdExists(req.params.accountId, res, next)
}) })
} }
] ]
@ -90,6 +96,28 @@ const videoChannelsGetValidator = [
} }
] ]
const videoChannelsShareValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelShare parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await isVideoChannelExistsPromise(req.params.id, res)) return
const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId)
if (!share) {
return res.status(404)
.end()
}
res.locals.videoChannelShare = share
return next()
}
]
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
@ -97,7 +125,8 @@ export {
videoChannelsAddValidator, videoChannelsAddValidator,
videoChannelsUpdateValidator, videoChannelsUpdateValidator,
videoChannelsRemoveValidator, videoChannelsRemoveValidator,
videoChannelsGetValidator videoChannelsGetValidator,
videoChannelsShareValidator
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -24,7 +24,8 @@ import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers'
import { database as db } from '../../initializers/database' import { database as db } from '../../initializers/database'
import { UserInstance } from '../../models/account/user-interface' import { UserInstance } from '../../models/account/user-interface'
import { authenticate } from '../oauth' import { authenticate } from '../oauth'
import { checkErrors } from './utils' import { areValidationErrors, checkErrors } from './utils'
import { isVideoExistsPromise } from '../../helpers/index'
const videosAddValidator = [ const videosAddValidator = [
body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
@ -230,6 +231,28 @@ const videoRateValidator = [
} }
] ]
const videosShareValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoShare parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await isVideoExistsPromise(req.params.id, res)) return
const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id)
if (!share) {
return res.status(404)
.end()
}
res.locals.videoShare = share
return next()
}
]
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
@ -238,6 +261,7 @@ export {
videosGetValidator, videosGetValidator,
videosRemoveValidator, videosRemoveValidator,
videosSearchValidator, videosSearchValidator,
videosShareValidator,
videoAbuseReportValidator, videoAbuseReportValidator,

View File

@ -6,6 +6,7 @@ import { VideoChannelObject } from '../../../shared/models/activitypub/objects/v
import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model'
import { AccountInstance } from '../account/account-interface' import { AccountInstance } from '../account/account-interface'
import { VideoInstance } from './video-interface' import { VideoInstance } from './video-interface'
import { VideoChannelShareInstance } from './video-channel-share-interface'
export namespace VideoChannelMethods { export namespace VideoChannelMethods {
export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel
@ -47,6 +48,7 @@ export interface VideoChannelAttributes {
Account?: AccountInstance Account?: AccountInstance
Videos?: VideoInstance[] Videos?: VideoInstance[]
VideoChannelShares?: VideoChannelShareInstance[]
} }
export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> { export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> {

View File

@ -5,10 +5,12 @@ import { VideoChannelInstance } from './video-channel-interface'
export namespace VideoChannelShareMethods { export namespace VideoChannelShareMethods {
export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]> export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]>
export type Load = (accountId: number, videoId: number) => Bluebird<VideoChannelShareInstance>
} }
export interface VideoChannelShareClass { export interface VideoChannelShareClass {
loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
load: VideoChannelShareMethods.Load
} }
export interface VideoChannelShareAttributes { export interface VideoChannelShareAttributes {

View File

@ -5,6 +5,7 @@ import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelSha
let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes>
let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
let load: VideoChannelShareMethods.Load
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare', VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare',
@ -23,6 +24,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
const classMethods = [ const classMethods = [
associate, associate,
load,
loadAccountsByShare loadAccountsByShare
] ]
addMethodsToModel(VideoChannelShare, classMethods) addMethodsToModel(VideoChannelShare, classMethods)
@ -50,6 +52,19 @@ function associate (models) {
}) })
} }
load = function (accountId: number, videoChannelId: number) {
return VideoChannelShare.findOne({
where: {
accountId,
videoChannelId
},
include: [
VideoChannelShare['sequelize'].models.Account,
VideoChannelShare['sequelize'].models.VideoChannel
]
})
}
loadAccountsByShare = function (videoChannelId: number) { loadAccountsByShare = function (videoChannelId: number) {
const query = { const query = {
where: { where: {

View File

@ -6,6 +6,8 @@ import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete'
import { addMethodsToModel, getSort } from '../utils' import { addMethodsToModel, getSort } from '../utils'
import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface' import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface'
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
import { activityPubCollection } from '../../helpers/activitypub'
let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
let toFormattedJSON: VideoChannelMethods.ToFormattedJSON let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
@ -139,6 +141,18 @@ toFormattedJSON = function (this: VideoChannelInstance) {
} }
toActivityPubObject = function (this: VideoChannelInstance) { toActivityPubObject = function (this: VideoChannelInstance) {
let sharesObject
if (Array.isArray(this.VideoChannelShares)) {
const shares: string[] = []
for (const videoChannelShare of this.VideoChannelShares) {
const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account)
shares.push(shareUrl)
}
sharesObject = activityPubCollection(shares)
}
const json = { const json = {
type: 'VideoChannel' as 'VideoChannel', type: 'VideoChannel' as 'VideoChannel',
id: this.url, id: this.url,
@ -146,7 +160,8 @@ toActivityPubObject = function (this: VideoChannelInstance) {
content: this.description, content: this.description,
name: this.name, name: this.name,
published: this.createdAt.toISOString(), published: this.createdAt.toISOString(),
updated: this.updatedAt.toISOString() updated: this.updatedAt.toISOString(),
shares: sharesObject
} }
return json return json

View File

@ -1,14 +1,16 @@
import * as Bluebird from 'bluebird'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { AccountInstance } from '../account/account-interface' import { AccountInstance } from '../account/account-interface'
import { VideoInstance } from './video-interface' import { VideoInstance } from './video-interface'
import * as Bluebird from 'bluebird'
export namespace VideoShareMethods { export namespace VideoShareMethods {
export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]> export type LoadAccountsByShare = (videoId: number) => Bluebird<AccountInstance[]>
export type Load = (accountId: number, videoId: number) => Bluebird<VideoShareInstance>
} }
export interface VideoShareClass { export interface VideoShareClass {
loadAccountsByShare: VideoShareMethods.LoadAccountsByShare loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
load: VideoShareMethods.Load
} }
export interface VideoShareAttributes { export interface VideoShareAttributes {

View File

@ -5,6 +5,7 @@ import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './v
let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes> let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes>
let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
let load: VideoShareMethods.Load
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare', VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare',
@ -23,7 +24,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
const classMethods = [ const classMethods = [
associate, associate,
loadAccountsByShare loadAccountsByShare,
load
] ]
addMethodsToModel(VideoShare, classMethods) addMethodsToModel(VideoShare, classMethods)
@ -50,6 +52,18 @@ function associate (models) {
}) })
} }
load = function (accountId: number, videoId: number) {
return VideoShare.findOne({
where: {
accountId,
videoId
},
include: [
VideoShare['sequelize'].models.Account
]
})
}
loadAccountsByShare = function (videoId: number) { loadAccountsByShare = function (videoId: number) {
const query = { const query = {
where: { where: {

View File

@ -32,6 +32,7 @@ import { isVideoNameValid, isVideoLicenceValid, isVideoNSFWValid, isVideoDescrip
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils' import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils'
import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils' import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils'
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
let Video: Sequelize.Model<VideoInstance, VideoAttributes> let Video: Sequelize.Model<VideoInstance, VideoAttributes>
let getOriginalFile: VideoMethods.GetOriginalFile let getOriginalFile: VideoMethods.GetOriginalFile
@ -573,6 +574,18 @@ toActivityPubObject = function (this: VideoInstance) {
dislikesObject = activityPubCollection(dislikes) dislikesObject = activityPubCollection(dislikes)
} }
let sharesObject
if (Array.isArray(this.VideoShares)) {
const shares: string[] = []
for (const videoShare of this.VideoShares) {
const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account)
shares.push(shareUrl)
}
sharesObject = activityPubCollection(shares)
}
const url = [] const url = []
for (const file of this.VideoFiles) { for (const file of this.VideoFiles) {
url.push({ url.push({
@ -630,7 +643,8 @@ toActivityPubObject = function (this: VideoInstance) {
}, },
url, url,
likes: likesObject, likes: likesObject,
dislikes: dislikesObject dislikes: dislikesObject,
shares: sharesObject
} }
return videoObject return videoObject
@ -823,7 +837,8 @@ listAllAndSharedByAccountForOutbox = function (accountId: number, start: number,
accountId accountId
} }
] ]
} },
include: [ Video['sequelize'].models.Account ]
}, },
{ {
model: Video['sequelize'].models.VideoChannel, model: Video['sequelize'].models.VideoChannel,

View File

@ -1,3 +1,5 @@
import { ActivityPubOrderedCollection } from '../activitypub-ordered-collection'
export interface VideoChannelObject { export interface VideoChannelObject {
type: 'VideoChannel' type: 'VideoChannel'
id: string id: string
@ -7,4 +9,5 @@ export interface VideoChannelObject {
published: string published: string
updated: string updated: string
actor?: string actor?: string
shares?: ActivityPubOrderedCollection<string>
} }

View File

@ -27,4 +27,5 @@ export interface VideoTorrentObject {
actor?: string actor?: string
likes?: ActivityPubOrderedCollection<string> likes?: ActivityPubOrderedCollection<string>
dislikes?: ActivityPubOrderedCollection<string> dislikes?: ActivityPubOrderedCollection<string>
shares?: ActivityPubOrderedCollection<string>
} }