+ @HasMany(() => UserVideoHistoryModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+ UserVideoHistories: UserVideoHistoryModel[]
+
+ @HasOne(() => ScheduleVideoUpdateModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+ ScheduleVideoUpdate: ScheduleVideoUpdateModel
+
+ @HasOne(() => VideoBlacklistModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+ VideoBlacklist: VideoBlacklistModel
+
+ @HasOne(() => VideoImportModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: true
+ },
+ onDelete: 'set null'
+ })
+ VideoImport: VideoImportModel
+
+ @HasMany(() => VideoCaptionModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'cascade',
+ hooks: true,
+ [ 'separate' as any ]: true
+ })
+ VideoCaptions: VideoCaptionModel[]
+
+ @BeforeDestroy
+ static async sendDelete (instance: VideoModel, options) {
+ if (instance.isOwned()) {
+ if (!instance.VideoChannel) {
+ instance.VideoChannel = await instance.$get('VideoChannel', {
+ include: [
+ {
+ model: AccountModel,
+ include: [ ActorModel ]
+ }
+ ],
+ transaction: options.transaction
+ }) as VideoChannelModel
+ }
+
+ return sendDeleteVideo(instance, options.transaction)
+ }
+
+ return undefined
+ }
+
+ @BeforeDestroy
+ static async removeFiles (instance: VideoModel) {
+ const tasks: Promise<any>[] = []
+
+ logger.info('Removing files of video %s.', instance.url)
+
+ if (instance.isOwned()) {
+ if (!Array.isArray(instance.VideoFiles)) {
+ instance.VideoFiles = await instance.$get('VideoFiles') as VideoFileModel[]
+ }
+
+ // Remove physical files and torrents
+ instance.VideoFiles.forEach(file => {
+ tasks.push(instance.removeFile(file))
+ tasks.push(instance.removeTorrent(file))
+ })
+
+ // Remove playlists file
+ tasks.push(instance.removeStreamingPlaylist())
+ }
+
+ // Do not wait video deletion because we could be in a transaction
+ Promise.all(tasks)
+ .catch(err => {
+ logger.error('Some errors when removing files of video %s in before destroy hook.', instance.uuid, { err })
+ })
+
+ return undefined
+ }
+
+ static listLocal () {
+ const query = {
+ where: {
+ remote: false
+ }
+ }
+
+ return VideoModel.scope([
+ ScopeNames.WITH_FILES,
+ ScopeNames.WITH_STREAMING_PLAYLISTS,
+ ScopeNames.WITH_THUMBNAILS
+ ]).findAll(query)
+ }
+
+ static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) {
+ function getRawQuery (select: string) {
+ const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' +
+ 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
+ 'INNER JOIN "account" AS "Account" ON "Account"."id" = "VideoChannel"."accountId" ' +
+ 'WHERE "Account"."actorId" = ' + actorId
+ const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' +
+ 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
+ 'WHERE "VideoShare"."actorId" = ' + actorId
+
+ return `(${queryVideo}) UNION (${queryVideoShare})`
+ }
+
+ const rawQuery = getRawQuery('"Video"."id"')
+ const rawCountQuery = getRawQuery('COUNT("Video"."id") as "total"')
+
+ const query = {
+ distinct: true,
+ offset: start,
+ limit: count,
+ order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings
+ where: {
+ id: {
+ [ Op.in ]: Sequelize.literal('(' + rawQuery + ')')
+ },
+ [ Op.or ]: [
+ { privacy: VideoPrivacy.PUBLIC },
+ { privacy: VideoPrivacy.UNLISTED }
+ ]
+ },
+ include: [
+ {
+ attributes: [ 'language' ],
+ model: VideoCaptionModel.unscoped(),
+ required: false
+ },
+ {
+ attributes: [ 'id', 'url' ],
+ model: VideoShareModel.unscoped(),
+ required: false,
+ // We only want videos shared by this actor
+ where: {
+ [ Op.and ]: [
+ {
+ id: {
+ [ Op.not ]: null
+ }
+ },
+ {
+ actorId
+ }
+ ]
+ },
+ include: [
+ {
+ attributes: [ 'id', 'url' ],
+ model: ActorModel.unscoped()
+ }
+ ]
+ },
+ {
+ model: VideoChannelModel.unscoped(),
+ required: true,
+ include: [
+ {
+ attributes: [ 'name' ],
+ model: AccountModel.unscoped(),
+ required: true,
+ include: [
+ {
+ attributes: [ 'id', 'url', 'followersUrl' ],
+ model: ActorModel.unscoped(),
+ required: true
+ }
+ ]
+ },
+ {
+ attributes: [ 'id', 'url', 'followersUrl' ],
+ model: ActorModel.unscoped(),
+ required: true
+ }
+ ]
+ },
+ VideoFileModel,
+ TagModel
+ ]
+ }
+
+ return Bluebird.all([
+ VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query),
+ VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT })
+ ]).then(([ rows, totals ]) => {
+ // totals: totalVideos + totalVideoShares
+ let totalVideos = 0
+ let totalVideoShares = 0
+ if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
+ if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
+
+ const total = totalVideos + totalVideoShares
+ return {
+ data: rows,
+ total: total
+ }