Check activities host
This commit is contained in:
parent
df66d81583
commit
5c6d985fae
|
@ -6,11 +6,11 @@ import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } fr
|
||||||
import { ResultList } from '../../../../../shared/models/result-list.model'
|
import { ResultList } from '../../../../../shared/models/result-list.model'
|
||||||
import {
|
import {
|
||||||
UserVideoRate,
|
UserVideoRate,
|
||||||
|
UserVideoRateType,
|
||||||
UserVideoRateUpdate,
|
UserVideoRateUpdate,
|
||||||
VideoConstant,
|
VideoConstant,
|
||||||
VideoFilter,
|
VideoFilter,
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideoRateType,
|
|
||||||
VideoUpdate
|
VideoUpdate
|
||||||
} from '../../../../../shared/models/videos'
|
} from '../../../../../shared/models/videos'
|
||||||
import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum'
|
import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum'
|
||||||
|
@ -332,7 +332,7 @@ export class VideoService implements VideosProvider {
|
||||||
return privacies
|
return privacies
|
||||||
}
|
}
|
||||||
|
|
||||||
private setVideoRate (id: number, rateType: VideoRateType) {
|
private setVideoRate (id: number, rateType: UserVideoRateType) {
|
||||||
const url = VideoService.BASE_VIDEO_URL + id + '/rate'
|
const url = VideoService.BASE_VIDEO_URL + id + '/rate'
|
||||||
const body: UserVideoRateUpdate = {
|
const body: UserVideoRateUpdate = {
|
||||||
rating: rateType
|
rating: rateType
|
||||||
|
|
|
@ -450,7 +450,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
this.checkUserRating()
|
this.checkUserRating()
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRating (nextRating: VideoRateType) {
|
private setRating (nextRating: UserVideoRateType) {
|
||||||
let method
|
let method
|
||||||
switch (nextRating) {
|
switch (nextRating) {
|
||||||
case 'like':
|
case 'like':
|
||||||
|
@ -476,7 +476,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) {
|
private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {
|
||||||
let likesToIncrement = 0
|
let likesToIncrement = 0
|
||||||
let dislikesToIncrement = 0
|
let dislikesToIncrement = 0
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { VideoModel } from '../server/models/video/video'
|
||||||
import { ActorModel } from '../server/models/activitypub/actor'
|
import { ActorModel } from '../server/models/activitypub/actor'
|
||||||
import {
|
import {
|
||||||
getAccountActivityPubUrl,
|
getAccountActivityPubUrl,
|
||||||
getAnnounceActivityPubUrl,
|
getVideoAnnounceActivityPubUrl,
|
||||||
getVideoActivityPubUrl, getVideoChannelActivityPubUrl,
|
getVideoActivityPubUrl, getVideoChannelActivityPubUrl,
|
||||||
getVideoCommentActivityPubUrl
|
getVideoCommentActivityPubUrl
|
||||||
} from '../server/lib/activitypub'
|
} from '../server/lib/activitypub'
|
||||||
|
@ -78,7 +78,7 @@ async function run () {
|
||||||
|
|
||||||
console.log('Updating video share ' + videoShare.url)
|
console.log('Updating video share ' + videoShare.url)
|
||||||
|
|
||||||
videoShare.url = getAnnounceActivityPubUrl(videoShare.Video.url, videoShare.Actor)
|
videoShare.url = getVideoAnnounceActivityPubUrl(videoShare.Actor, videoShare.Video)
|
||||||
await videoShare.save()
|
await videoShare.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,22 @@ import * as express from 'express'
|
||||||
import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
|
import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
|
||||||
import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
|
import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
|
||||||
import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers'
|
import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers'
|
||||||
import { buildAnnounceWithVideoAudience } from '../../lib/activitypub/send'
|
import { buildAnnounceWithVideoAudience, buildDislikeActivity, buildLikeActivity } from '../../lib/activitypub/send'
|
||||||
import { audiencify, getAudience } from '../../lib/activitypub/audience'
|
import { audiencify, getAudience } from '../../lib/activitypub/audience'
|
||||||
import { buildCreateActivity } from '../../lib/activitypub/send/send-create'
|
import { buildCreateActivity } from '../../lib/activitypub/send/send-create'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
|
videosShareValidator,
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
localAccountValidator,
|
localAccountValidator,
|
||||||
localVideoChannelValidator,
|
localVideoChannelValidator,
|
||||||
videosCustomGetValidator
|
videosCustomGetValidator
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
import { videoCommentGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators'
|
import {
|
||||||
|
getAccountVideoRateValidator,
|
||||||
|
videoCommentGetValidator,
|
||||||
|
videosGetValidator
|
||||||
|
} from '../../middlewares/validators'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
import { ActorModel } from '../../models/activitypub/actor'
|
import { ActorModel } from '../../models/activitypub/actor'
|
||||||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||||
|
@ -25,6 +30,7 @@ import { cacheRoute } from '../../middlewares/cache'
|
||||||
import { activityPubResponse } from './utils'
|
import { activityPubResponse } from './utils'
|
||||||
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
||||||
import {
|
import {
|
||||||
|
getRateUrl,
|
||||||
getVideoCommentsActivityPubUrl,
|
getVideoCommentsActivityPubUrl,
|
||||||
getVideoDislikesActivityPubUrl,
|
getVideoDislikesActivityPubUrl,
|
||||||
getVideoLikesActivityPubUrl,
|
getVideoLikesActivityPubUrl,
|
||||||
|
@ -48,6 +54,14 @@ activityPubClientRouter.get('/accounts?/:name/following',
|
||||||
executeIfActivityPub(asyncMiddleware(localAccountValidator)),
|
executeIfActivityPub(asyncMiddleware(localAccountValidator)),
|
||||||
executeIfActivityPub(asyncMiddleware(accountFollowingController))
|
executeIfActivityPub(asyncMiddleware(accountFollowingController))
|
||||||
)
|
)
|
||||||
|
activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
|
||||||
|
executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('like'))),
|
||||||
|
executeIfActivityPub(getAccountVideoRate('like'))
|
||||||
|
)
|
||||||
|
activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
|
||||||
|
executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('dislike'))),
|
||||||
|
executeIfActivityPub(getAccountVideoRate('dislike'))
|
||||||
|
)
|
||||||
|
|
||||||
activityPubClientRouter.get('/videos/watch/:id',
|
activityPubClientRouter.get('/videos/watch/:id',
|
||||||
executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
|
executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
|
||||||
|
@ -62,7 +76,7 @@ activityPubClientRouter.get('/videos/watch/:id/announces',
|
||||||
executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))),
|
executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))),
|
||||||
executeIfActivityPub(asyncMiddleware(videoAnnouncesController))
|
executeIfActivityPub(asyncMiddleware(videoAnnouncesController))
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/announces/:accountId',
|
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||||
executeIfActivityPub(asyncMiddleware(videosShareValidator)),
|
executeIfActivityPub(asyncMiddleware(videosShareValidator)),
|
||||||
executeIfActivityPub(asyncMiddleware(videoAnnounceController))
|
executeIfActivityPub(asyncMiddleware(videoAnnounceController))
|
||||||
)
|
)
|
||||||
|
@ -133,6 +147,20 @@ async function accountFollowingController (req: express.Request, res: express.Re
|
||||||
return activityPubResponse(activityPubContextify(activityPubResult), res)
|
return activityPubResponse(activityPubContextify(activityPubResult), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAccountVideoRate (rateType: VideoRateType) {
|
||||||
|
return (req: express.Request, res: express.Response) => {
|
||||||
|
const accountVideoRate: AccountVideoRateModel = res.locals.accountVideoRate
|
||||||
|
|
||||||
|
const byActor = accountVideoRate.Account.Actor
|
||||||
|
const url = getRateUrl(rateType, byActor, accountVideoRate.Video)
|
||||||
|
const APObject = rateType === 'like'
|
||||||
|
? buildLikeActivity(url, byActor, accountVideoRate.Video)
|
||||||
|
: buildCreateActivity(url, byActor, buildDislikeActivity(url, byActor, accountVideoRate.Video))
|
||||||
|
|
||||||
|
return activityPubResponse(activityPubContextify(APObject), res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const video: VideoModel = res.locals.video
|
const video: VideoModel = res.locals.video
|
||||||
|
|
||||||
|
@ -276,7 +304,7 @@ function videoRates (req: express.Request, rateType: VideoRateType, video: Video
|
||||||
const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
|
const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
|
||||||
return {
|
return {
|
||||||
total: result.count,
|
total: result.count,
|
||||||
data: result.rows.map(r => r.Account.Actor.url)
|
data: result.rows.map(r => r.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return activityPubCollectionPagination(url, handler, req.query.page)
|
return activityPubCollectionPagination(url, handler, req.query.page)
|
||||||
|
|
|
@ -43,11 +43,13 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const inboxQueue = queue<{ activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel }, Error>((task, cb) => {
|
const inboxQueue = queue<{ activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel }, Error>((task, cb) => {
|
||||||
processActivities(task.activities, task.signatureActor, task.inboxActor)
|
const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor }
|
||||||
|
|
||||||
|
processActivities(task.activities, options)
|
||||||
.then(() => cb())
|
.then(() => cb())
|
||||||
})
|
})
|
||||||
|
|
||||||
function inboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function inboxController (req: express.Request, res: express.Response) {
|
||||||
const rootActivity: RootActivity = req.body
|
const rootActivity: RootActivity = req.body
|
||||||
let activities: Activity[] = []
|
let activities: Activity[] = []
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ import * as express from 'express'
|
||||||
import { UserVideoRateUpdate } from '../../../../shared'
|
import { UserVideoRateUpdate } from '../../../../shared'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers'
|
import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers'
|
||||||
import { sendVideoRateChange } from '../../../lib/activitypub'
|
import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub'
|
||||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
|
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares'
|
||||||
import { AccountModel } from '../../../models/account/account'
|
import { AccountModel } from '../../../models/account/account'
|
||||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
@ -12,7 +12,7 @@ const rateVideoRouter = express.Router()
|
||||||
|
|
||||||
rateVideoRouter.put('/:id/rate',
|
rateVideoRouter.put('/:id/rate',
|
||||||
authenticate,
|
authenticate,
|
||||||
asyncMiddleware(videoRateValidator),
|
asyncMiddleware(videoUpdateRateValidator),
|
||||||
asyncRetryTransactionMiddleware(rateVideo)
|
asyncRetryTransactionMiddleware(rateVideo)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,11 +28,12 @@ async function rateVideo (req: express.Request, res: express.Response) {
|
||||||
const body: UserVideoRateUpdate = req.body
|
const body: UserVideoRateUpdate = req.body
|
||||||
const rateType = body.rating
|
const rateType = body.rating
|
||||||
const videoInstance: VideoModel = res.locals.video
|
const videoInstance: VideoModel = res.locals.video
|
||||||
|
const userAccount: AccountModel = res.locals.oauth.token.User.Account
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
const sequelizeOptions = { transaction: t }
|
const sequelizeOptions = { transaction: t }
|
||||||
|
|
||||||
const accountInstance = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
|
const accountInstance = await AccountModel.load(userAccount.id, t)
|
||||||
const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t)
|
const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t)
|
||||||
|
|
||||||
let likesToIncrement = 0
|
let likesToIncrement = 0
|
||||||
|
@ -44,20 +45,22 @@ async function rateVideo (req: express.Request, res: express.Response) {
|
||||||
// There was a previous rate, update it
|
// There was a previous rate, update it
|
||||||
if (previousRate) {
|
if (previousRate) {
|
||||||
// We will remove the previous rate, so we will need to update the video count attribute
|
// We will remove the previous rate, so we will need to update the video count attribute
|
||||||
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
|
if (previousRate.type === 'like') likesToIncrement--
|
||||||
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
|
else if (previousRate.type === 'dislike') dislikesToIncrement--
|
||||||
|
|
||||||
if (rateType === 'none') { // Destroy previous rate
|
if (rateType === 'none') { // Destroy previous rate
|
||||||
await previousRate.destroy(sequelizeOptions)
|
await previousRate.destroy(sequelizeOptions)
|
||||||
} else { // Update previous rate
|
} else { // Update previous rate
|
||||||
previousRate.type = rateType
|
previousRate.type = rateType
|
||||||
|
previousRate.url = getRateUrl(rateType, userAccount.Actor, videoInstance)
|
||||||
await previousRate.save(sequelizeOptions)
|
await previousRate.save(sequelizeOptions)
|
||||||
}
|
}
|
||||||
} else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
|
} else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
|
||||||
const query = {
|
const query = {
|
||||||
accountId: accountInstance.id,
|
accountId: accountInstance.id,
|
||||||
videoId: videoInstance.id,
|
videoId: videoInstance.id,
|
||||||
type: rateType
|
type: rateType,
|
||||||
|
url: getRateUrl(rateType, userAccount.Actor, videoInstance)
|
||||||
}
|
}
|
||||||
|
|
||||||
await AccountVideoRateModel.create(query, sequelizeOptions)
|
await AccountVideoRateModel.create(query, sequelizeOptions)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ACTIVITY_PUB } from '../initializers'
|
||||||
import { ActorModel } from '../models/activitypub/actor'
|
import { ActorModel } from '../models/activitypub/actor'
|
||||||
import { signJsonLDObject } from './peertube-crypto'
|
import { signJsonLDObject } from './peertube-crypto'
|
||||||
import { pageToStartAndCount } from './core-utils'
|
import { pageToStartAndCount } from './core-utils'
|
||||||
|
import { parse } from 'url'
|
||||||
|
|
||||||
function activityPubContextify <T> (data: T) {
|
function activityPubContextify <T> (data: T) {
|
||||||
return Object.assign(data, {
|
return Object.assign(data, {
|
||||||
|
@ -111,9 +112,17 @@ function getActorUrl (activityActor: string | ActivityPubActor) {
|
||||||
return activityActor.id
|
return activityActor.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkUrlsSameHost (url1: string, url2: string) {
|
||||||
|
const idHost = parse(url1).host
|
||||||
|
const actorHost = parse(url2).host
|
||||||
|
|
||||||
|
return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
checkUrlsSameHost,
|
||||||
getActorUrl,
|
getActorUrl,
|
||||||
activityPubContextify,
|
activityPubContextify,
|
||||||
activityPubCollectionPagination,
|
activityPubCollectionPagination,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { createWriteStream } from 'fs-extra'
|
||||||
import * as request from 'request'
|
import * as request from 'request'
|
||||||
import { ACTIVITY_PUB } from '../initializers'
|
import { ACTIVITY_PUB } from '../initializers'
|
||||||
|
|
||||||
function doRequest (
|
function doRequest <T> (
|
||||||
requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }
|
requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }
|
||||||
): Bluebird<{ response: request.RequestResponse, body: any }> {
|
): Bluebird<{ response: request.RequestResponse, body: any }> {
|
||||||
if (requestOptions.activityPub === true) {
|
if (requestOptions.activityPub === true) {
|
||||||
|
@ -11,7 +11,7 @@ function doRequest (
|
||||||
requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER
|
requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Bluebird<{ response: request.RequestResponse, body: any }>((res, rej) => {
|
return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => {
|
||||||
request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
|
request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ let config: IConfig = require('config')
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 285
|
const LAST_MIGRATION_VERSION = 290
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -336,6 +336,9 @@ const CONSTRAINTS_FIELDS = {
|
||||||
VIDEOS_REDUNDANCY: {
|
VIDEOS_REDUNDANCY: {
|
||||||
URL: { min: 3, max: 2000 } // Length
|
URL: { min: 3, max: 2000 } // Length
|
||||||
},
|
},
|
||||||
|
VIDEO_RATES: {
|
||||||
|
URL: { min: 3, max: 2000 } // Length
|
||||||
|
},
|
||||||
VIDEOS: {
|
VIDEOS: {
|
||||||
NAME: { min: 3, max: 120 }, // Length
|
NAME: { min: 3, max: 120 }, // Length
|
||||||
LANGUAGE: { min: 1, max: 10 }, // Length
|
LANGUAGE: { min: 1, max: 10 }, // Length
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction,
|
||||||
|
queryInterface: Sequelize.QueryInterface,
|
||||||
|
sequelize: Sequelize.Sequelize,
|
||||||
|
db: any
|
||||||
|
}): Promise<void> {
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.STRING(2000),
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.queryInterface.addColumn('accountVideoRate', 'url', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const builtUrlQuery = `SELECT "actor"."url" || '/' || "accountVideoRate"."type" || 's/' || "videoId" ` +
|
||||||
|
'FROM "accountVideoRate" ' +
|
||||||
|
'INNER JOIN account ON account.id = "accountVideoRate"."accountId" ' +
|
||||||
|
'INNER JOIN actor ON actor.id = account."actorId" ' +
|
||||||
|
'WHERE "base".id = "accountVideoRate".id'
|
||||||
|
|
||||||
|
const query = 'UPDATE "accountVideoRate" base SET "url" = (' + builtUrlQuery + ') WHERE "url" IS NULL'
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.STRING(2000),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: null
|
||||||
|
}
|
||||||
|
await utils.queryInterface.changeColumn('accountVideoRate', 'url', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
up,
|
||||||
|
down
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import * as url from 'url'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import * as uuidv4 from 'uuid/v4'
|
||||||
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
|
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
|
||||||
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
|
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
|
||||||
import { getActorUrl } from '../../helpers/activitypub'
|
import { checkUrlsSameHost, getActorUrl } from '../../helpers/activitypub'
|
||||||
import { isActorObjectValid, normalizeActor } from '../../helpers/custom-validators/activitypub/actor'
|
import { isActorObjectValid, normalizeActor } from '../../helpers/custom-validators/activitypub/actor'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
|
import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
|
||||||
|
@ -65,8 +65,12 @@ async function getOrCreateActorAndServerAndModel (
|
||||||
const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
|
const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
|
||||||
if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
|
if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) {
|
||||||
|
throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Assert we don't recurse another time
|
// Don't recurse another time
|
||||||
ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, 'all', false)
|
ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, 'all', false)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Cannot get or create account attributed to video channel ' + actor.url)
|
logger.error('Cannot get or create account attributed to video channel ' + actor.url)
|
||||||
|
@ -297,12 +301,15 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
|
||||||
normalizeActor(requestResult.body)
|
normalizeActor(requestResult.body)
|
||||||
|
|
||||||
const actorJSON: ActivityPubActor = requestResult.body
|
const actorJSON: ActivityPubActor = requestResult.body
|
||||||
|
|
||||||
if (isActorObjectValid(actorJSON) === false) {
|
if (isActorObjectValid(actorJSON) === false) {
|
||||||
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
|
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
|
||||||
return { result: undefined, statusCode: requestResult.response.statusCode }
|
return { result: undefined, statusCode: requestResult.response.statusCode }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(actorJSON.id, actorUrl) !== true) {
|
||||||
|
throw new Error('Actor url ' + actorUrl + ' has not the same host than its AP id ' + actorJSON.id)
|
||||||
|
}
|
||||||
|
|
||||||
const followersCount = await fetchActorTotalItems(actorJSON.followers)
|
const followersCount = await fetchActorTotalItems(actorJSON.followers)
|
||||||
const followingCount = await fetchActorTotalItems(actorJSON.following)
|
const followingCount = await fetchActorTotalItems(actorJSON.following)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { ACTIVITY_PUB, JOB_REQUEST_TIMEOUT } from '../../initializers'
|
||||||
import { doRequest } from '../../helpers/requests'
|
import { doRequest } from '../../helpers/requests'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
|
import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
|
||||||
|
|
||||||
async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Promise<any> | Bluebird<any>) {
|
async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Promise<any> | Bluebird<any>) {
|
||||||
logger.info('Crawling ActivityPub data on %s.', uri)
|
logger.info('Crawling ActivityPub data on %s.', uri)
|
||||||
|
@ -14,7 +15,7 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr
|
||||||
timeout: JOB_REQUEST_TIMEOUT
|
timeout: JOB_REQUEST_TIMEOUT
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await doRequest(options)
|
const response = await doRequest<ActivityPubOrderedCollection<T>>(options)
|
||||||
const firstBody = response.body
|
const firstBody = response.body
|
||||||
|
|
||||||
let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
|
let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
|
||||||
|
@ -23,7 +24,7 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr
|
||||||
while (nextLink && i < limit) {
|
while (nextLink && i < limit) {
|
||||||
options.uri = nextLink
|
options.uri = nextLink
|
||||||
|
|
||||||
const { body } = await doRequest(options)
|
const { body } = await doRequest<ActivityPubOrderedCollection<T>>(options)
|
||||||
nextLink = body.next
|
nextLink = body.next
|
||||||
i++
|
i++
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1 @@
|
||||||
export * from './process'
|
export * from './process'
|
||||||
export * from './process-accept'
|
|
||||||
export * from './process-announce'
|
|
||||||
export * from './process-create'
|
|
||||||
export * from './process-delete'
|
|
||||||
export * from './process-follow'
|
|
||||||
export * from './process-like'
|
|
||||||
export * from './process-undo'
|
|
||||||
export * from './process-update'
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
|
||||||
import { forwardVideoRelatedActivity } from '../send/utils'
|
import { forwardVideoRelatedActivity } from '../send/utils'
|
||||||
import { Redis } from '../../redis'
|
import { Redis } from '../../redis'
|
||||||
import { createOrUpdateCacheFile } from '../cache-file'
|
import { createOrUpdateCacheFile } from '../cache-file'
|
||||||
|
import { immutableAssign } from '../../../tests/utils'
|
||||||
|
import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||||
|
|
||||||
async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
|
async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
|
||||||
const activityObject = activity.object
|
const activityObject = activity.object
|
||||||
|
@ -65,9 +67,10 @@ async function processCreateDislike (byActor: ActorModel, activity: ActivityCrea
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
accountId: byAccount.id
|
accountId: byAccount.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ , created ] = await AccountVideoRateModel.findOrCreate({
|
const [ , created ] = await AccountVideoRateModel.findOrCreate({
|
||||||
where: rate,
|
where: rate,
|
||||||
defaults: rate,
|
defaults: immutableAssign(rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }),
|
||||||
transaction: t
|
transaction: t
|
||||||
})
|
})
|
||||||
if (created === true) await video.increment('dislikes', { transaction: t })
|
if (created === true) await video.increment('dislikes', { transaction: t })
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { AccountVideoRateModel } from '../../../models/account/account-video-rat
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { forwardVideoRelatedActivity } from '../send/utils'
|
import { forwardVideoRelatedActivity } from '../send/utils'
|
||||||
import { getOrCreateVideoAndAccountAndChannel } from '../videos'
|
import { getOrCreateVideoAndAccountAndChannel } from '../videos'
|
||||||
|
import { immutableAssign } from '../../../tests/utils'
|
||||||
|
import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||||
|
|
||||||
async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) {
|
async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) {
|
||||||
return retryTransactionWrapper(processLikeVideo, byActor, activity)
|
return retryTransactionWrapper(processLikeVideo, byActor, activity)
|
||||||
|
@ -34,7 +36,7 @@ async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) {
|
||||||
}
|
}
|
||||||
const [ , created ] = await AccountVideoRateModel.findOrCreate({
|
const [ , created ] = await AccountVideoRateModel.findOrCreate({
|
||||||
where: rate,
|
where: rate,
|
||||||
defaults: rate,
|
defaults: immutableAssign(rate, { url: getVideoLikeActivityPubUrl(byActor, video) }),
|
||||||
transaction: t
|
transaction: t
|
||||||
})
|
})
|
||||||
if (created === true) await video.increment('likes', { transaction: t })
|
if (created === true) await video.increment('likes', { transaction: t })
|
||||||
|
|
|
@ -55,7 +55,8 @@ async function processUndoLike (byActor: ActorModel, activity: ActivityUndo) {
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
|
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
|
||||||
|
|
||||||
const rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
|
let rate = await AccountVideoRateModel.loadByUrl(likeActivity.id, t)
|
||||||
|
if (!rate) rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
|
||||||
if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
|
if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
|
||||||
|
|
||||||
await rate.destroy({ transaction: t })
|
await rate.destroy({ transaction: t })
|
||||||
|
@ -78,7 +79,8 @@ async function processUndoDislike (byActor: ActorModel, activity: ActivityUndo)
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
|
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
|
||||||
|
|
||||||
const rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
|
let rate = await AccountVideoRateModel.loadByUrl(dislike.id, t)
|
||||||
|
if (!rate) rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
|
||||||
if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
|
if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
|
||||||
|
|
||||||
await rate.destroy({ transaction: t })
|
await rate.destroy({ transaction: t })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
||||||
import { getActorUrl } from '../../../helpers/activitypub'
|
import { checkUrlsSameHost, getActorUrl } from '../../../helpers/activitypub'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { processAcceptActivity } from './process-accept'
|
import { processAcceptActivity } from './process-accept'
|
||||||
|
@ -25,11 +25,17 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: Ac
|
||||||
Like: processLikeActivity
|
Like: processLikeActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processActivities (activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel) {
|
async function processActivities (
|
||||||
|
activities: Activity[],
|
||||||
|
options: {
|
||||||
|
signatureActor?: ActorModel
|
||||||
|
inboxActor?: ActorModel
|
||||||
|
outboxUrl?: string
|
||||||
|
} = {}) {
|
||||||
const actorsCache: { [ url: string ]: ActorModel } = {}
|
const actorsCache: { [ url: string ]: ActorModel } = {}
|
||||||
|
|
||||||
for (const activity of activities) {
|
for (const activity of activities) {
|
||||||
if (!signatureActor && [ 'Create', 'Announce', 'Like' ].indexOf(activity.type) === -1) {
|
if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].indexOf(activity.type) === -1) {
|
||||||
logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
|
logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -37,12 +43,17 @@ async function processActivities (activities: Activity[], signatureActor?: Actor
|
||||||
const actorUrl = getActorUrl(activity.actor)
|
const actorUrl = getActorUrl(activity.actor)
|
||||||
|
|
||||||
// When we fetch remote data, we don't have signature
|
// When we fetch remote data, we don't have signature
|
||||||
if (signatureActor && actorUrl !== signatureActor.url) {
|
if (options.signatureActor && actorUrl !== options.signatureActor.url) {
|
||||||
logger.warn('Signature mismatch between %s and %s.', actorUrl, signatureActor.url)
|
logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, options.signatureActor.url)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const byActor = signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
|
if (options.outboxUrl && checkUrlsSameHost(options.outboxUrl, actorUrl) !== true) {
|
||||||
|
logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', options.outboxUrl, actorUrl)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const byActor = options.signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
|
||||||
actorsCache[actorUrl] = byActor
|
actorsCache[actorUrl] = byActor
|
||||||
|
|
||||||
const activityProcessor = processActivity[activity.type]
|
const activityProcessor = processActivity[activity.type]
|
||||||
|
@ -52,7 +63,7 @@ async function processActivities (activities: Activity[], signatureActor?: Actor
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await activityProcessor(activity, byActor, inboxActor)
|
await activityProcessor(activity, byActor, options.inboxActor)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot process activity %s.', activity.type, { err })
|
logger.warn('Cannot process activity %s.', activity.type, { err })
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transa
|
||||||
logger.info('Creating job to send view of %s.', video.url)
|
logger.info('Creating job to send view of %s.', video.url)
|
||||||
|
|
||||||
const url = getVideoViewActivityPubUrl(byActor, video)
|
const url = getVideoViewActivityPubUrl(byActor, video)
|
||||||
const viewActivity = buildViewActivity(byActor, video)
|
const viewActivity = buildViewActivity(url, byActor, video)
|
||||||
|
|
||||||
return sendVideoRelatedCreateActivity({
|
return sendVideoRelatedCreateActivity({
|
||||||
// Use the server actor to send the view
|
// Use the server actor to send the view
|
||||||
|
@ -111,7 +111,7 @@ async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Tra
|
||||||
logger.info('Creating job to dislike %s.', video.url)
|
logger.info('Creating job to dislike %s.', video.url)
|
||||||
|
|
||||||
const url = getVideoDislikeActivityPubUrl(byActor, video)
|
const url = getVideoDislikeActivityPubUrl(byActor, video)
|
||||||
const dislikeActivity = buildDislikeActivity(byActor, video)
|
const dislikeActivity = buildDislikeActivity(url, byActor, video)
|
||||||
|
|
||||||
return sendVideoRelatedCreateActivity({
|
return sendVideoRelatedCreateActivity({
|
||||||
byActor,
|
byActor,
|
||||||
|
@ -136,16 +136,18 @@ function buildCreateActivity (url: string, byActor: ActorModel, object: any, aud
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDislikeActivity (byActor: ActorModel, video: VideoModel) {
|
function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel) {
|
||||||
return {
|
return {
|
||||||
|
id: url,
|
||||||
type: 'Dislike',
|
type: 'Dislike',
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: video.url
|
object: video.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildViewActivity (byActor: ActorModel, video: VideoModel) {
|
function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel) {
|
||||||
return {
|
return {
|
||||||
|
id: url,
|
||||||
type: 'View',
|
type: 'View',
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: video.url
|
object: video.url
|
||||||
|
|
|
@ -24,8 +24,8 @@ function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel,
|
||||||
|
|
||||||
return audiencify(
|
return audiencify(
|
||||||
{
|
{
|
||||||
type: 'Like' as 'Like',
|
|
||||||
id: url,
|
id: url,
|
||||||
|
type: 'Like' as 'Like',
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: video.url
|
object: video.url
|
||||||
},
|
},
|
||||||
|
|
|
@ -64,7 +64,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
|
||||||
logger.info('Creating job to undo a dislike of video %s.', video.url)
|
logger.info('Creating job to undo a dislike of video %s.', video.url)
|
||||||
|
|
||||||
const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
|
const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
|
||||||
const dislikeActivity = buildDislikeActivity(byActor, video)
|
const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video)
|
||||||
const createDislikeActivity = buildCreateActivity(dislikeUrl, byActor, dislikeActivity)
|
const createDislikeActivity = buildCreateActivity(dislikeUrl, byActor, dislikeActivity)
|
||||||
|
|
||||||
return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: createDislikeActivity, transaction: t })
|
return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: createDislikeActivity, transaction: t })
|
||||||
|
|
|
@ -4,13 +4,14 @@ import { getServerActor } from '../../helpers/utils'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
|
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
|
||||||
import { getAnnounceActivityPubUrl } from './url'
|
import { getVideoAnnounceActivityPubUrl } from './url'
|
||||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import { doRequest } from '../../helpers/requests'
|
import { doRequest } from '../../helpers/requests'
|
||||||
import { getOrCreateActorAndServerAndModel } from './actor'
|
import { getOrCreateActorAndServerAndModel } from './actor'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
|
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
|
||||||
|
import { checkUrlsSameHost, getActorUrl } from '../../helpers/activitypub'
|
||||||
|
|
||||||
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
|
||||||
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
|
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
|
||||||
|
@ -38,9 +39,13 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
|
||||||
json: true,
|
json: true,
|
||||||
activityPub: true
|
activityPub: true
|
||||||
})
|
})
|
||||||
if (!body || !body.actor) throw new Error('Body of body actor is invalid')
|
if (!body || !body.actor) throw new Error('Body or body actor is invalid')
|
||||||
|
|
||||||
|
const actorUrl = getActorUrl(body.actor)
|
||||||
|
if (checkUrlsSameHost(shareUrl, actorUrl) !== true) {
|
||||||
|
throw new Error(`Actor url ${actorUrl} has not the same host than the share url ${shareUrl}`)
|
||||||
|
}
|
||||||
|
|
||||||
const actorUrl = body.actor
|
|
||||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
|
@ -72,7 +77,7 @@ export {
|
||||||
async function shareByServer (video: VideoModel, t: Transaction) {
|
async function shareByServer (video: VideoModel, t: Transaction) {
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor)
|
const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
|
||||||
return VideoShareModel.findOrCreate({
|
return VideoShareModel.findOrCreate({
|
||||||
defaults: {
|
defaults: {
|
||||||
actorId: serverActor.id,
|
actorId: serverActor.id,
|
||||||
|
@ -91,7 +96,7 @@ async function shareByServer (video: VideoModel, t: Transaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shareByVideoChannel (video: VideoModel, t: Transaction) {
|
async function shareByVideoChannel (video: VideoModel, t: Transaction) {
|
||||||
const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor)
|
const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
|
||||||
return VideoShareModel.findOrCreate({
|
return VideoShareModel.findOrCreate({
|
||||||
defaults: {
|
defaults: {
|
||||||
actorId: video.VideoChannel.actorId,
|
actorId: video.VideoChannel.actorId,
|
||||||
|
|
|
@ -33,14 +33,14 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
||||||
return video.url + '/views/' + byActor.uuid + '/' + new Date().toISOString()
|
return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoLikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
function getVideoLikeActivityPubUrl (byActor: ActorModel, video: VideoModel | { id: number }) {
|
||||||
return byActor.url + '/likes/' + video.id
|
return byActor.url + '/likes/' + video.id
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoDislikeActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
function getVideoDislikeActivityPubUrl (byActor: ActorModel, video: VideoModel | { id: number }) {
|
||||||
return byActor.url + '/dislikes/' + video.id
|
return byActor.url + '/dislikes/' + video.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +74,8 @@ function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) {
|
||||||
return follower.url + '/accepts/follows/' + me.id
|
return follower.url + '/accepts/follows/' + me.id
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnnounceActivityPubUrl (originalUrl: string, byActor: ActorModel) {
|
function getVideoAnnounceActivityPubUrl (byActor: ActorModel, video: VideoModel) {
|
||||||
return originalUrl + '/announces/' + byActor.id
|
return video.url + '/announces/' + byActor.id
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeleteActivityPubUrl (originalUrl: string) {
|
function getDeleteActivityPubUrl (originalUrl: string) {
|
||||||
|
@ -97,7 +97,7 @@ export {
|
||||||
getVideoAbuseActivityPubUrl,
|
getVideoAbuseActivityPubUrl,
|
||||||
getActorFollowActivityPubUrl,
|
getActorFollowActivityPubUrl,
|
||||||
getActorFollowAcceptActivityPubUrl,
|
getActorFollowAcceptActivityPubUrl,
|
||||||
getAnnounceActivityPubUrl,
|
getVideoAnnounceActivityPubUrl,
|
||||||
getUpdateActivityPubUrl,
|
getUpdateActivityPubUrl,
|
||||||
getUndoActivityPubUrl,
|
getUndoActivityPubUrl,
|
||||||
getVideoViewActivityPubUrl,
|
getVideoViewActivityPubUrl,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { VideoCommentModel } from '../../models/video/video-comment'
|
||||||
import { getOrCreateActorAndServerAndModel } from './actor'
|
import { getOrCreateActorAndServerAndModel } from './actor'
|
||||||
import { getOrCreateVideoAndAccountAndChannel } from './videos'
|
import { getOrCreateVideoAndAccountAndChannel } from './videos'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
|
import { checkUrlsSameHost } from '../../helpers/activitypub'
|
||||||
|
|
||||||
async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) {
|
async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) {
|
||||||
let originCommentId: number = null
|
let originCommentId: number = null
|
||||||
|
@ -61,6 +62,14 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
|
||||||
const actorUrl = body.attributedTo
|
const actorUrl = body.attributedTo
|
||||||
if (!actorUrl) return { created: false }
|
if (!actorUrl) return { created: false }
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(commentUrl, actorUrl) !== true) {
|
||||||
|
throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${commentUrl}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(body.id, commentUrl) !== true) {
|
||||||
|
throw new Error(`Comment url ${commentUrl} host is different from the AP object id ${body.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||||
const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
|
const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
|
||||||
if (!entry) return { created: false }
|
if (!entry) return { created: false }
|
||||||
|
@ -134,6 +143,14 @@ async function resolveThread (url: string, comments: VideoCommentModel[] = []) {
|
||||||
const actorUrl = body.attributedTo
|
const actorUrl = body.attributedTo
|
||||||
if (!actorUrl) throw new Error('Miss attributed to in comment')
|
if (!actorUrl) throw new Error('Miss attributed to in comment')
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(url, actorUrl) !== true) {
|
||||||
|
throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${url}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(body.id, url) !== true) {
|
||||||
|
throw new Error(`Comment url ${url} host is different from the AP object id ${body.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||||
const comment = new VideoCommentModel({
|
const comment = new VideoCommentModel({
|
||||||
url: body.id,
|
url: body.id,
|
||||||
|
|
|
@ -8,13 +8,35 @@ import { getOrCreateActorAndServerAndModel } from './actor'
|
||||||
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
|
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
|
||||||
|
import { doRequest } from '../../helpers/requests'
|
||||||
|
import { checkUrlsSameHost, getActorUrl } from '../../helpers/activitypub'
|
||||||
|
import { ActorModel } from '../../models/activitypub/actor'
|
||||||
|
import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
|
||||||
|
|
||||||
async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
|
async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) {
|
||||||
let rateCounts = 0
|
let rateCounts = 0
|
||||||
|
|
||||||
await Bluebird.map(actorUrls, async actorUrl => {
|
await Bluebird.map(ratesUrl, async rateUrl => {
|
||||||
try {
|
try {
|
||||||
|
// Fetch url
|
||||||
|
const { body } = await doRequest({
|
||||||
|
uri: rateUrl,
|
||||||
|
json: true,
|
||||||
|
activityPub: true
|
||||||
|
})
|
||||||
|
if (!body || !body.actor) throw new Error('Body or body actor is invalid')
|
||||||
|
|
||||||
|
const actorUrl = getActorUrl(body.actor)
|
||||||
|
if (checkUrlsSameHost(actorUrl, rateUrl) !== true) {
|
||||||
|
throw new Error(`Rate url ${rateUrl} has not the same host than actor url ${actorUrl}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(body.id, rateUrl) !== true) {
|
||||||
|
throw new Error(`Rate url ${rateUrl} host is different from the AP object id ${body.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||||
|
|
||||||
const [ , created ] = await AccountVideoRateModel
|
const [ , created ] = await AccountVideoRateModel
|
||||||
.findOrCreate({
|
.findOrCreate({
|
||||||
where: {
|
where: {
|
||||||
|
@ -24,13 +46,14 @@ async function createRates (actorUrls: string[], video: VideoModel, rate: VideoR
|
||||||
defaults: {
|
defaults: {
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
accountId: actor.Account.id,
|
accountId: actor.Account.id,
|
||||||
type: rate
|
type: rate,
|
||||||
|
url: body.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (created) rateCounts += 1
|
if (created) rateCounts += 1
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot add rate %s for actor %s.', rate, actorUrl, { err })
|
logger.warn('Cannot add rate %s.', rateUrl, { err })
|
||||||
}
|
}
|
||||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||||
|
|
||||||
|
@ -62,7 +85,12 @@ async function sendVideoRateChange (account: AccountModel,
|
||||||
if (dislikes > 0) await sendCreateDislike(actor, video, t)
|
if (dislikes > 0) await sendCreateDislike(actor, video, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) {
|
||||||
|
return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video)
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
getRateUrl,
|
||||||
createRates,
|
createRates,
|
||||||
sendVideoRateChange
|
sendVideoRateChange
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { createRates } from './video-rates'
|
||||||
import { addVideoShares, shareVideoByServerAndChannel } from './share'
|
import { addVideoShares, shareVideoByServerAndChannel } from './share'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
|
import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
|
||||||
|
import { checkUrlsSameHost } from '../../helpers/activitypub'
|
||||||
|
|
||||||
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
||||||
// If the video is not private and published, we federate it
|
// If the video is not private and published, we federate it
|
||||||
|
@ -63,7 +64,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.
|
||||||
|
|
||||||
const { response, body } = await doRequest(options)
|
const { response, body } = await doRequest(options)
|
||||||
|
|
||||||
if (sanitizeAndCheckVideoTorrentObject(body) === false) {
|
if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
|
||||||
logger.debug('Remote video JSON is not valid.', { body })
|
logger.debug('Remote video JSON is not valid.', { body })
|
||||||
return { response, videoObject: undefined }
|
return { response, videoObject: undefined }
|
||||||
}
|
}
|
||||||
|
@ -107,6 +108,10 @@ function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject
|
||||||
const channel = videoObject.attributedTo.find(a => a.type === 'Group')
|
const channel = videoObject.attributedTo.find(a => a.type === 'Group')
|
||||||
if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
|
if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
|
||||||
|
|
||||||
|
if (checkUrlsSameHost(channel.id, videoObject.id) !== true) {
|
||||||
|
throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${videoObject.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
return getOrCreateActorAndServerAndModel(channel.id, 'all')
|
return getOrCreateActorAndServerAndModel(channel.id, 'all')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
|
||||||
if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
|
if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
|
||||||
|
|
||||||
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
|
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
|
||||||
'activity': items => processActivities(items),
|
'activity': items => processActivities(items, { outboxUrl: payload.uri }),
|
||||||
'video-likes': items => createRates(items, video, 'like'),
|
'video-likes': items => createRates(items, video, 'like'),
|
||||||
'video-dislikes': items => createRates(items, video, 'dislike'),
|
'video-dislikes': items => createRates(items, video, 'dislike'),
|
||||||
'video-shares': items => addVideoShares(items, video),
|
'video-shares': items => addVideoShares(items, video),
|
||||||
|
|
|
@ -5,4 +5,6 @@ export * from './video-channels'
|
||||||
export * from './video-comments'
|
export * from './video-comments'
|
||||||
export * from './video-imports'
|
export * from './video-imports'
|
||||||
export * from './video-watch'
|
export * from './video-watch'
|
||||||
|
export * from './video-rates'
|
||||||
|
export * from './video-shares'
|
||||||
export * from './videos'
|
export * from './videos'
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import * as express from 'express'
|
||||||
|
import 'express-validator'
|
||||||
|
import { body, param } from 'express-validator/check'
|
||||||
|
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
|
||||||
|
import { isVideoExist, isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos'
|
||||||
|
import { logger } from '../../../helpers/logger'
|
||||||
|
import { areValidationErrors } from '../utils'
|
||||||
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
||||||
|
import { VideoRateType } from '../../../../shared/models/videos'
|
||||||
|
import { isAccountNameValid } from '../../../helpers/custom-validators/accounts'
|
||||||
|
|
||||||
|
const videoUpdateRateValidator = [
|
||||||
|
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||||
|
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
|
||||||
|
|
||||||
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking videoRate parameters', { parameters: req.body })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
if (!await isVideoExist(req.params.id, res)) return
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const getAccountVideoRateValidator = function (rateType: VideoRateType) {
|
||||||
|
return [
|
||||||
|
param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
|
||||||
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||||
|
|
||||||
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
|
const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, req.params.videoId)
|
||||||
|
if (!rate) {
|
||||||
|
return res.status(404)
|
||||||
|
.json({ error: 'Video rate not found' })
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.accountVideoRate = rate
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
videoUpdateRateValidator,
|
||||||
|
getAccountVideoRateValidator
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import * as express from 'express'
|
||||||
|
import 'express-validator'
|
||||||
|
import { param } from 'express-validator/check'
|
||||||
|
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
|
||||||
|
import { isVideoExist } from '../../../helpers/custom-validators/videos'
|
||||||
|
import { logger } from '../../../helpers/logger'
|
||||||
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
|
import { areValidationErrors } from '../utils'
|
||||||
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
|
||||||
|
const videosShareValidator = [
|
||||||
|
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||||
|
param('actorId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid actor 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 isVideoExist(req.params.id, res)) return
|
||||||
|
|
||||||
|
const video: VideoModel = res.locals.video
|
||||||
|
|
||||||
|
const share = await VideoShareModel.load(req.params.actorId, video.id)
|
||||||
|
if (!share) {
|
||||||
|
return res.status(404)
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.videoShare = share
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
videosShareValidator
|
||||||
|
}
|
|
@ -26,14 +26,12 @@ import {
|
||||||
isVideoLicenceValid,
|
isVideoLicenceValid,
|
||||||
isVideoNameValid,
|
isVideoNameValid,
|
||||||
isVideoPrivacyValid,
|
isVideoPrivacyValid,
|
||||||
isVideoRatingTypeValid,
|
|
||||||
isVideoSupportValid,
|
isVideoSupportValid,
|
||||||
isVideoTagsValid
|
isVideoTagsValid
|
||||||
} from '../../../helpers/custom-validators/videos'
|
} from '../../../helpers/custom-validators/videos'
|
||||||
import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils'
|
import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../../initializers'
|
||||||
import { VideoShareModel } from '../../../models/video/video-share'
|
|
||||||
import { authenticate } from '../../oauth'
|
import { authenticate } from '../../oauth'
|
||||||
import { areValidationErrors } from '../utils'
|
import { areValidationErrors } from '../utils'
|
||||||
import { cleanUpReqFiles } from '../../../helpers/express-utils'
|
import { cleanUpReqFiles } from '../../../helpers/express-utils'
|
||||||
|
@ -188,41 +186,6 @@ const videosRemoveValidator = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const videoRateValidator = [
|
|
||||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
|
||||||
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
|
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
||||||
logger.debug('Checking videoRate parameters', { parameters: req.body })
|
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
|
||||||
if (!await isVideoExist(req.params.id, res)) return
|
|
||||||
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
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 isVideoExist(req.params.id, res)) return
|
|
||||||
|
|
||||||
const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
|
|
||||||
if (!share) {
|
|
||||||
return res.status(404)
|
|
||||||
.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.videoShare = share
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const videosChangeOwnershipValidator = [
|
const videosChangeOwnershipValidator = [
|
||||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||||
|
|
||||||
|
@ -415,9 +378,6 @@ export {
|
||||||
videosGetValidator,
|
videosGetValidator,
|
||||||
videosCustomGetValidator,
|
videosCustomGetValidator,
|
||||||
videosRemoveValidator,
|
videosRemoveValidator,
|
||||||
videosShareValidator,
|
|
||||||
|
|
||||||
videoRateValidator,
|
|
||||||
|
|
||||||
videosChangeOwnershipValidator,
|
videosChangeOwnershipValidator,
|
||||||
videosTerminateChangeOwnershipValidator,
|
videosTerminateChangeOwnershipValidator,
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { values } from 'lodash'
|
import { values } from 'lodash'
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
||||||
import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
|
import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
|
||||||
import { VideoRateType } from '../../../shared/models/videos'
|
import { VideoRateType } from '../../../shared/models/videos'
|
||||||
import { VIDEO_RATE_TYPES } from '../../initializers'
|
import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { AccountModel } from './account'
|
import { AccountModel } from './account'
|
||||||
import { ActorModel } from '../activitypub/actor'
|
import { ActorModel } from '../activitypub/actor'
|
||||||
|
import { throwIfNotValid } from '../utils'
|
||||||
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Account rates per video.
|
Account rates per video.
|
||||||
|
@ -26,6 +28,10 @@ import { ActorModel } from '../activitypub/actor'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: [ 'videoId', 'type' ]
|
fields: [ 'videoId', 'type' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: [ 'url' ],
|
||||||
|
unique: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -35,6 +41,11 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
|
||||||
@Column(DataType.ENUM(values(VIDEO_RATE_TYPES)))
|
@Column(DataType.ENUM(values(VIDEO_RATE_TYPES)))
|
||||||
type: VideoRateType
|
type: VideoRateType
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Is('AccountVideoRateUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
|
||||||
|
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_RATES.URL.max))
|
||||||
|
url: string
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
@ -65,7 +76,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
|
||||||
})
|
})
|
||||||
Account: AccountModel
|
Account: AccountModel
|
||||||
|
|
||||||
static load (accountId: number, videoId: number, transaction: Transaction) {
|
static load (accountId: number, videoId: number, transaction?: Transaction) {
|
||||||
const options: IFindOptions<AccountVideoRateModel> = {
|
const options: IFindOptions<AccountVideoRateModel> = {
|
||||||
where: {
|
where: {
|
||||||
accountId,
|
accountId,
|
||||||
|
@ -77,6 +88,49 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
|
||||||
return AccountVideoRateModel.findOne(options)
|
return AccountVideoRateModel.findOne(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) {
|
||||||
|
const options: IFindOptions<AccountVideoRateModel> = {
|
||||||
|
where: {
|
||||||
|
videoId,
|
||||||
|
type: rateType
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: AccountModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
attributes: [ 'id', 'url', 'preferredUsername' ],
|
||||||
|
model: ActorModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
where: {
|
||||||
|
preferredUsername: accountName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VideoModel.unscoped(),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (transaction) options.transaction = transaction
|
||||||
|
|
||||||
|
return AccountVideoRateModel.findOne(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadByUrl (url: string, transaction: Transaction) {
|
||||||
|
const options: IFindOptions<AccountVideoRateModel> = {
|
||||||
|
where: {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (transaction) options.transaction = transaction
|
||||||
|
|
||||||
|
return AccountVideoRateModel.findOne(options)
|
||||||
|
}
|
||||||
|
|
||||||
static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
|
static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
offset: start,
|
offset: start,
|
||||||
|
|
|
@ -47,7 +47,7 @@ enum ScopeNames {
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id', 'url' ],
|
||||||
model: () => ActorModel.unscoped(),
|
model: () => ActorModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
||||||
})
|
})
|
||||||
Video: VideoModel
|
Video: VideoModel
|
||||||
|
|
||||||
static load (actorId: number, videoId: number, t: Sequelize.Transaction) {
|
static load (actorId: number, videoId: number, t?: Sequelize.Transaction) {
|
||||||
return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
|
return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
|
||||||
where: {
|
where: {
|
||||||
actorId,
|
actorId,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
|
||||||
import { flushAndRunMultipleServers, flushTests, killallServers, makeAPRequest, makeFollowRequest, ServerInfo } from '../../utils'
|
import { flushAndRunMultipleServers, flushTests, killallServers, makePOSTAPRequest, makeFollowRequest, ServerInfo } from '../../utils'
|
||||||
import { HTTP_SIGNATURE } from '../../../initializers'
|
import { HTTP_SIGNATURE } from '../../../initializers'
|
||||||
import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
|
import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
|
@ -63,7 +63,7 @@ describe('Test ActivityPub security', function () {
|
||||||
Digest: buildDigest({ hello: 'coucou' })
|
Digest: buildDigest({ hello: 'coucou' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(403)
|
expect(response.statusCode).to.equal(403)
|
||||||
})
|
})
|
||||||
|
@ -73,7 +73,7 @@ describe('Test ActivityPub security', function () {
|
||||||
const headers = buildGlobalHeaders(body)
|
const headers = buildGlobalHeaders(body)
|
||||||
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
|
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(403)
|
expect(response.statusCode).to.equal(403)
|
||||||
})
|
})
|
||||||
|
@ -85,7 +85,7 @@ describe('Test ActivityPub security', function () {
|
||||||
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
|
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
|
||||||
const headers = buildGlobalHeaders(body)
|
const headers = buildGlobalHeaders(body)
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(403)
|
expect(response.statusCode).to.equal(403)
|
||||||
})
|
})
|
||||||
|
@ -97,7 +97,7 @@ describe('Test ActivityPub security', function () {
|
||||||
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
|
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
|
||||||
const headers = buildGlobalHeaders(body)
|
const headers = buildGlobalHeaders(body)
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(204)
|
expect(response.statusCode).to.equal(204)
|
||||||
})
|
})
|
||||||
|
@ -126,7 +126,7 @@ describe('Test ActivityPub security', function () {
|
||||||
|
|
||||||
const headers = buildGlobalHeaders(signedBody)
|
const headers = buildGlobalHeaders(signedBody)
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(403)
|
expect(response.statusCode).to.equal(403)
|
||||||
})
|
})
|
||||||
|
@ -147,7 +147,7 @@ describe('Test ActivityPub security', function () {
|
||||||
|
|
||||||
const headers = buildGlobalHeaders(signedBody)
|
const headers = buildGlobalHeaders(signedBody)
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(403)
|
expect(response.statusCode).to.equal(403)
|
||||||
})
|
})
|
||||||
|
@ -163,7 +163,7 @@ describe('Test ActivityPub security', function () {
|
||||||
|
|
||||||
const headers = buildGlobalHeaders(signedBody)
|
const headers = buildGlobalHeaders(signedBody)
|
||||||
|
|
||||||
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
|
const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
|
||||||
|
|
||||||
expect(response.statusCode).to.equal(204)
|
expect(response.statusCode).to.equal(204)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { HTTP_SIGNATURE } from '../../../initializers'
|
||||||
import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
|
import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
|
||||||
import { activityPubContextify } from '../../../helpers/activitypub'
|
import { activityPubContextify } from '../../../helpers/activitypub'
|
||||||
|
|
||||||
function makeAPRequest (url: string, body: any, httpSignature: any, headers: any) {
|
function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri: url,
|
uri: url,
|
||||||
|
@ -34,10 +34,10 @@ async function makeFollowRequest (to: { url: string }, by: { url: string, privat
|
||||||
}
|
}
|
||||||
const headers = buildGlobalHeaders(body)
|
const headers = buildGlobalHeaders(body)
|
||||||
|
|
||||||
return makeAPRequest(to.url, body, httpSignature, headers)
|
return makePOSTAPRequest(to.url, body, httpSignature, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
makeAPRequest,
|
makePOSTAPRequest,
|
||||||
makeFollowRequest
|
makeFollowRequest
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export interface DislikeObject {
|
export interface DislikeObject {
|
||||||
type: 'Dislike',
|
id: string
|
||||||
|
type: 'Dislike'
|
||||||
actor: string
|
actor: string
|
||||||
object: string
|
object: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export type VideoRateType = 'like' | 'dislike' | 'none'
|
export type VideoRateType = 'like' | 'dislike'
|
||||||
|
|
Loading…
Reference in New Issue