if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) {
activities = (rootActivity as ActivityPubCollection).items
} else if ([ 'OrderedCollection', 'OrderedCollectionPage' ].indexOf(rootActivity.type) !== -1) {
- activities = (rootActivity as ActivityPubOrderedCollection).orderedItems
+ activities = (rootActivity as ActivityPubOrderedCollection<Activity>).orderedItems
} else {
activities = [ rootActivity as Activity ]
}
})
}
+function activityPubCollection (results: any[]) {
+ return {
+ type: 'OrderedCollection',
+ totalItems: results.length,
+ orderedItems: results
+ }
+}
+
function activityPubCollectionPagination (url: string, page: any, result: ResultList<any>) {
let next: string
let prev: string
export {
activityPubContextify,
activityPubCollectionPagination,
+ activityPubCollection,
buildSignedActivity
}
import * as Bluebird from 'bluebird'
import { VideoTorrentObject } from '../../../../shared'
import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
+import { VideoRateType } from '../../../../shared/models/videos/video-rate.type'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger'
import { database as db } from '../../../initializers'
import { AccountInstance } from '../../../models/account/account-interface'
import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { VideoInstance } from '../../../models/video/video-interface'
import { getOrCreateAccountAndServer } from '../account'
import { getOrCreateVideoChannel } from '../video-channels'
import { generateThumbnailFromUrl } from '../videos'
// ---------------------------------------------------------------------------
-function processAddVideo (account: AccountInstance, activity: ActivityAdd, videoChannel: VideoChannelInstance, video: VideoTorrentObject) {
+async function processAddVideo (
+ account: AccountInstance,
+ activity: ActivityAdd,
+ videoChannel: VideoChannelInstance,
+ videoToCreateData: VideoTorrentObject
+) {
const options = {
- arguments: [ account, activity, videoChannel, video ],
+ arguments: [ account, activity, videoChannel, videoToCreateData ],
errorMessage: 'Cannot insert the remote video with many retries.'
}
- return retryTransactionWrapper(addRemoteVideo, options)
+ const video = await retryTransactionWrapper(addRemoteVideo, options)
+
+ // Process outside the transaction because we could fetch remote data
+ if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
+ await createRates(videoToCreateData.likes.orderedItems, video, 'like')
+ }
+
+ if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
+ await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
+ }
+
+ return video
}
function addRemoteVideo (account: AccountInstance,
return videoCreated
})
}
+
+async function createRates (accountUrls: string[], video: VideoInstance, rate: VideoRateType) {
+ let rateCounts = 0
+ const tasks: Bluebird<any>[] = []
+
+ for (const accountUrl of accountUrls) {
+ const account = await getOrCreateAccountAndServer(accountUrl)
+ const p = db.AccountVideoRate
+ .create({
+ videoId: video.id,
+ accountId: account.id,
+ type: rate
+ })
+ .then(() => rateCounts += 1)
+
+ tasks.push(p)
+ }
+
+ await Promise.all(tasks)
+
+ logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
+
+ // This is "likes" and "dislikes"
+ await video.increment(rate + 's', { by: rateCounts })
+
+ return
+}
continue
}
- await activityProcessor(activity, inboxAccount)
+ try {
+ await activityProcessor(activity, inboxAccount)
+ } catch (err) {
+ logger.warn('Cannot process activity %s.', activity.type, err)
+ }
}
}
}
function onError (err: Error, jobId: number) {
- logger.error('Error when broadcasting ActivityPub request in job %d.', jobId, err)
+ logger.error('Error when fetcher ActivityPub request in job %d.', jobId, err)
return Promise.resolve()
}
import * as Promise from 'bluebird'
import { VideoRateType } from '../../../shared/models/videos/video-rate.type'
+import { AccountInstance } from './account-interface'
export namespace AccountVideoRateMethods {
export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<AccountVideoRateInstance>
type: VideoRateType
accountId: number
videoId: number
+
+ Account?: AccountInstance
}
export interface AccountVideoRateInstance
import { VideoChannelInstance } from './video-channel-interface'
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
import { VideoShareInstance } from './video-share-interface'
+import { UserVideoRate } from '../../../shared/models/videos/user-video-rate.model'
+import { AccountVideoRateInstance } from '../account/account-video-rate-interface'
export namespace VideoMethods {
export type GetThumbnailName = (this: VideoInstance) => string
Tags?: TagInstance[]
VideoFiles?: VideoFileInstance[]
VideoShares?: VideoShareInstance[]
+ AccountVideoRates?: AccountVideoRateInstance[]
}
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
import { sendDeleteVideo } from '../../lib/index'
import * as Bluebird from 'bluebird'
+import { activityPubCollection } from '../../helpers/activitypub'
const Buffer = safeBuffer.Buffer
},
onDelete: 'cascade'
})
+
+ Video.hasMany(models.AccountVideoRate, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
}
function afterDestroy (video: VideoInstance) {
}
}
+ let likesObject
+ let dislikesObject
+
+ if (Array.isArray(this.AccountVideoRates)) {
+ const likes: string[] = []
+ const dislikes: string[] = []
+
+ for (const rate of this.AccountVideoRates) {
+ if (rate.type === 'like') {
+ likes.push(rate.Account.url)
+ } else if (rate.type === 'dislike') {
+ dislikes.push(rate.Account.url)
+ }
+ }
+
+ likesObject = activityPubCollection(likes)
+ dislikesObject = activityPubCollection(dislikes)
+ }
+
const url = []
for (const file of this.VideoFiles) {
url.push({
width: THUMBNAILS_SIZE.width,
height: THUMBNAILS_SIZE.height
},
- url // FIXME: needed?
+ url,
+ likes: likesObject,
+ dislikes: dislikesObject
}
return videoObject
}
]
},
- Video['sequelize'].models.Tag,
- Video['sequelize'].models.VideoFile
+ {
+ model: Video['sequelize'].models.AccountVideoRate,
+ include: [ Video['sequelize'].models.Account ]
+ },
+ Video['sequelize'].models.VideoFile,
+ Video['sequelize'].models.Tag
]
}
}
]
},
+ {
+ model: Video['sequelize'].models.AccountVideoRate,
+ include: [ Video['sequelize'].models.Account ]
+ },
Video['sequelize'].models.Tag,
Video['sequelize'].models.VideoFile
]
}
]
},
+ {
+ model: Video['sequelize'].models.AccountVideoRate,
+ include: [ Video['sequelize'].models.Account ]
+ },
Video['sequelize'].models.Tag,
Video['sequelize'].models.VideoFile
]
import { Activity } from './activity'
-export interface ActivityPubOrderedCollection {
+export interface ActivityPubOrderedCollection<T> {
'@context': string[]
type: 'OrderedCollection' | 'OrderedCollectionPage'
totalItems: number
partOf?: string
- orderedItems: Activity[]
+ orderedItems: T[]
}
import { ActivityPubCollection } from './activitypub-collection'
import { ActivityPubOrderedCollection } from './activitypub-ordered-collection'
-export type RootActivity = Activity | ActivityPubCollection | ActivityPubOrderedCollection
+export type RootActivity = Activity | ActivityPubCollection | ActivityPubOrderedCollection<Activity>
ActivityTagObject,
ActivityUrlObject
} from './common-objects'
+import { ActivityPubOrderedCollection } from '../activitypub-ordered-collection'
export interface VideoTorrentObject {
type: 'Video'
icon: ActivityIconObject
url: ActivityUrlObject[]
actor?: string
+ likes?: ActivityPubOrderedCollection<string>
+ dislikes?: ActivityPubOrderedCollection<string>
}