]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
Add downloadingEnabled property to video model
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index ce856aed2c9ce7926e2ce3a0f5b562ba3494a98a..a2fe53fb90bec9f78e407d192f3a468ee7eddfdf 100644 (file)
@@ -91,6 +91,8 @@ import {
   videoModelToFormattedDetailsJSON,
   videoModelToFormattedJSON
 } from './video-format-utils'
+import * as validator from 'validator'
+import { UserVideoHistoryModel } from '../account/user-video-history'
 
 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
 const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -126,7 +128,8 @@ export enum ScopeNames {
   WITH_TAGS = 'WITH_TAGS',
   WITH_FILES = 'WITH_FILES',
   WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
-  WITH_BLACKLISTED = 'WITH_BLACKLISTED'
+  WITH_BLACKLISTED = 'WITH_BLACKLISTED',
+  WITH_USER_HISTORY = 'WITH_USER_HISTORY'
 }
 
 type ForAPIOptions = {
@@ -463,9 +466,12 @@ type AvailableForListIDsOptions = {
     include: [
       {
         model: () => VideoFileModel.unscoped(),
+        // FIXME: typings
+        [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
         required: false,
         include: [
           {
+            attributes: [ 'fileUrl' ],
             model: () => VideoRedundancyModel.unscoped(),
             required: false
           }
@@ -480,6 +486,20 @@ type AvailableForListIDsOptions = {
         required: false
       }
     ]
+  },
+  [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => {
+    return {
+      include: [
+        {
+          attributes: [ 'currentTime' ],
+          model: UserVideoHistoryModel.unscoped(),
+          required: false,
+          where: {
+            userId
+          }
+        }
+      ]
+    }
   }
 })
 @Table({
@@ -578,6 +598,10 @@ export class VideoModel extends Model<VideoModel> {
   @Column
   commentsEnabled: boolean
 
+  @AllowNull(false)
+  @Column
+  downloadingEnabled: boolean
+
   @AllowNull(false)
   @Column
   waitTranscoding: boolean
@@ -670,11 +694,19 @@ export class VideoModel extends Model<VideoModel> {
       name: 'videoId',
       allowNull: false
     },
-    onDelete: 'cascade',
-    hooks: true
+    onDelete: 'cascade'
   })
   VideoViews: VideoViewModel[]
 
+  @HasMany(() => UserVideoHistoryModel, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+  UserVideoHistories: UserVideoHistoryModel[]
+
   @HasOne(() => ScheduleVideoUpdateModel, {
     foreignKey: {
       name: 'videoId',
@@ -928,7 +960,8 @@ export class VideoModel extends Model<VideoModel> {
     accountId?: number,
     videoChannelId?: number,
     actorId?: number
-    trendingDays?: number
+    trendingDays?: number,
+    userId?: number
   }, countVideos = true) {
     const query: IFindOptions<VideoModel> = {
       offset: options.start,
@@ -959,6 +992,7 @@ export class VideoModel extends Model<VideoModel> {
       accountId: options.accountId,
       videoChannelId: options.videoChannelId,
       includeLocalVideos: options.includeLocalVideos,
+      userId: options.userId,
       trendingDays
     }
 
@@ -981,6 +1015,7 @@ export class VideoModel extends Model<VideoModel> {
     tagsAllOf?: string[]
     durationMin?: number // seconds
     durationMax?: number // seconds
+    userId?: number
   }) {
     const whereAnd = []
 
@@ -1056,14 +1091,33 @@ export class VideoModel extends Model<VideoModel> {
       licenceOneOf: options.licenceOneOf,
       languageOneOf: options.languageOneOf,
       tagsOneOf: options.tagsOneOf,
-      tagsAllOf: options.tagsAllOf
+      tagsAllOf: options.tagsAllOf,
+      userId: options.userId
     }
 
     return VideoModel.getAvailableForApi(query, queryOptions)
   }
 
-  static load (id: number, t?: Sequelize.Transaction) {
-    return VideoModel.findById(id, { transaction: t })
+  static load (id: number | string, t?: Sequelize.Transaction) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+    const options = {
+      where,
+      transaction: t
+    }
+
+    return VideoModel.findOne(options)
+  }
+
+  static loadOnlyId (id: number | string, t?: Sequelize.Transaction) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+
+    const options = {
+      attributes: [ 'id' ],
+      where,
+      transaction: t
+    }
+
+    return VideoModel.findOne(options)
   }
 
   static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) {
@@ -1071,63 +1125,63 @@ export class VideoModel extends Model<VideoModel> {
                      .findById(id, { transaction: t, logging })
   }
 
-  static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
-    const query: IFindOptions<VideoModel> = {
+  static loadByUUIDWithFile (uuid: string) {
+    const options = {
       where: {
-        url
+        uuid
       }
     }
 
-    if (t !== undefined) query.transaction = t
-
-    return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query)
+    return VideoModel
+      .scope([ ScopeNames.WITH_FILES ])
+      .findOne(options)
   }
 
-  static loadAndPopulateAccountAndServerAndTags (id: number) {
-    const options = {
-      order: [ [ 'Tags', 'name', 'ASC' ] ]
+  static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
+    const query: IFindOptions<VideoModel> = {
+      where: {
+        url
+      },
+      transaction
     }
 
-    return VideoModel
-      .scope([
-        ScopeNames.WITH_TAGS,
-        ScopeNames.WITH_BLACKLISTED,
-        ScopeNames.WITH_FILES,
-        ScopeNames.WITH_ACCOUNT_DETAILS,
-        ScopeNames.WITH_SCHEDULED_UPDATE
-      ])
-      .findById(id, options)
+    return VideoModel.findOne(query)
   }
 
-  static loadByUUID (uuid: string) {
-    const options = {
+  static loadByUrlAndPopulateAccount (url: string, transaction?: Sequelize.Transaction) {
+    const query: IFindOptions<VideoModel> = {
       where: {
-        uuid
-      }
+        url
+      },
+      transaction
     }
 
-    return VideoModel
-      .scope([ ScopeNames.WITH_FILES ])
-      .findOne(options)
+    return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query)
   }
 
-  static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string, t?: Sequelize.Transaction) {
+  static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+
     const options = {
       order: [ [ 'Tags', 'name', 'ASC' ] ],
-      where: {
-        uuid
-      },
+      where,
       transaction: t
     }
 
+    const scopes = [
+      ScopeNames.WITH_TAGS,
+      ScopeNames.WITH_BLACKLISTED,
+      ScopeNames.WITH_FILES,
+      ScopeNames.WITH_ACCOUNT_DETAILS,
+      ScopeNames.WITH_SCHEDULED_UPDATE
+    ]
+
+    if (userId) {
+      scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
+    }
+
     return VideoModel
-      .scope([
-        ScopeNames.WITH_TAGS,
-        ScopeNames.WITH_BLACKLISTED,
-        ScopeNames.WITH_FILES,
-        ScopeNames.WITH_ACCOUNT_DETAILS,
-        ScopeNames.WITH_SCHEDULED_UPDATE
-      ])
+      .scope(scopes)
       .findOne(options)
   }
 
@@ -1211,7 +1265,11 @@ export class VideoModel extends Model<VideoModel> {
     return {}
   }
 
-  private static async getAvailableForApi (query: IFindOptions<VideoModel>, options: AvailableForListIDsOptions, countVideos = true) {
+  private static async getAvailableForApi (
+    query: IFindOptions<VideoModel>,
+    options: AvailableForListIDsOptions & { userId?: number},
+    countVideos = true
+  ) {
     const idsScope = {
       method: [
         ScopeNames.AVAILABLE_FOR_LIST_IDS, options
@@ -1235,8 +1293,15 @@ export class VideoModel extends Model<VideoModel> {
 
     if (ids.length === 0) return { data: [], total: count }
 
-    const apiScope = {
-      method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ]
+    // FIXME: typings
+    const apiScope: any[] = [
+      {
+        method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ]
+      }
+    ]
+
+    if (options.userId) {
+      apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.userId ] })
     }
 
     const secondQuery = {
@@ -1277,6 +1342,10 @@ export class VideoModel extends Model<VideoModel> {
     return VIDEO_STATES[ id ] || 'Unknown'
   }
 
+  static buildWhereIdOrUUID (id: number | string) {
+    return validator.isInt('' + id) ? { id } : { uuid: id }
+  }
+
   getOriginalFile () {
     if (Array.isArray(this.VideoFiles) === false) return undefined
 
@@ -1398,7 +1467,7 @@ export class VideoModel extends Model<VideoModel> {
     return getVideoFileResolution(originalFilePath)
   }
 
-  getDescriptionPath () {
+  getDescriptionAPIPath () {
     return `/api/${API_VERSION}/videos/${this.uuid}/description`
   }