]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video-import.ts
Add ability to list imports of a channel sync
[github/Chocobozzz/PeerTube.git] / server / models / video / video-import.ts
index fbe0ee0a740844ae5094b538951f4645ed32be69..da6b92c7a1e6672dffbc3135ff713cba2c8a2d06 100644 (file)
@@ -1,3 +1,4 @@
+import { IncludeOptions, Op, WhereOptions } from 'sequelize'
 import {
   AfterUpdate,
   AllowNull,
@@ -13,15 +14,25 @@ import {
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
-import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
-import { getSort, throwIfNotValid } from '../utils'
-import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
+import { afterCommitIfTransaction } from '@server/helpers/database-utils'
+import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import'
+import { VideoImport, VideoImportState } from '@shared/models'
+import { AttributesOnly } from '@shared/typescript-utils'
 import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
-import { VideoImport, VideoImportState } from '../../../shared'
 import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
-import { UserModel } from '../account/user'
-import * as Bluebird from 'bluebird'
-import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import'
+import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
+import { UserModel } from '../user/user'
+import { getSort, searchAttribute, throwIfNotValid } from '../utils'
+import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
+import { VideoChannelSyncModel } from './video-channel-sync'
+
+const defaultVideoScope = () => {
+  return VideoModel.scope([
+    VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
+    VideoModelScopeNames.WITH_TAGS,
+    VideoModelScopeNames.WITH_THUMBNAILS
+  ])
+}
 
 @DefaultScope(() => ({
   include: [
@@ -30,11 +41,11 @@ import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/mo
       required: true
     },
     {
-      model: VideoModel.scope([
-        VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
-        VideoModelScopeNames.WITH_TAGS,
-        VideoModelScopeNames.WITH_THUMBNAILS
-      ]),
+      model: defaultVideoScope(),
+      required: false
+    },
+    {
+      model: VideoChannelSyncModel.unscoped(),
       required: false
     }
   ]
@@ -52,7 +63,7 @@ import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/mo
     }
   ]
 })
-export class VideoImportModel extends Model<VideoImportModel> {
+export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> {
   @CreatedAt
   createdAt: Date
 
@@ -111,44 +122,107 @@ export class VideoImportModel extends Model<VideoImportModel> {
   })
   Video: VideoModel
 
+  @ForeignKey(() => VideoChannelSyncModel)
+  @Column
+  videoChannelSyncId: number
+
+  @BelongsTo(() => VideoChannelSyncModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'set null'
+  })
+  VideoChannelSync: VideoChannelSyncModel
+
   @AfterUpdate
   static deleteVideoIfFailed (instance: VideoImportModel, options) {
     if (instance.state === VideoImportState.FAILED) {
-      return instance.Video.destroy({ transaction: options.transaction })
+      return afterCommitIfTransaction(options.transaction, () => instance.Video.destroy())
     }
 
     return undefined
   }
 
-  static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> {
+  static loadAndPopulateVideo (id: number): Promise<MVideoImportDefault> {
     return VideoImportModel.findByPk(id)
   }
 
-  static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) {
+  static listUserVideoImportsForApi (options: {
+    userId: number
+    start: number
+    count: number
+    sort: string
+
+    search?: string
+    targetUrl?: string
+    videoChannelSyncId?: number
+  }) {
+    const { userId, start, count, sort, targetUrl, videoChannelSyncId, search } = options
+
+    const where: WhereOptions = { userId }
+    const include: IncludeOptions[] = [
+      {
+        attributes: [ 'id' ],
+        model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
+        required: true
+      },
+      {
+        model: VideoChannelSyncModel.unscoped(),
+        required: false
+      }
+    ]
+
+    if (targetUrl) where['targetUrl'] = targetUrl
+    if (videoChannelSyncId) where['videoChannelSyncId'] = videoChannelSyncId
+
+    if (search) {
+      include.push({
+        model: defaultVideoScope(),
+        required: true,
+        where: searchAttribute(search, 'name')
+      })
+    } else {
+      include.push({
+        model: defaultVideoScope(),
+        required: false
+      })
+    }
+
     const query = {
       distinct: true,
-      include: [
-        {
-          attributes: [ 'id' ],
-          model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
-          required: true
-        }
-      ],
+      include,
       offset: start,
       limit: count,
       order: getSort(sort),
-      where: {
-        userId
-      }
+      where
     }
 
-    return VideoImportModel.findAndCountAll<MVideoImportDefault>(query)
-                           .then(({ rows, count }) => {
-                             return {
-                               data: rows,
-                               total: count
-                             }
-                           })
+    return Promise.all([
+      VideoImportModel.unscoped().count(query),
+      VideoImportModel.findAll<MVideoImportDefault>(query)
+    ]).then(([ total, data ]) => ({ total, data }))
+  }
+
+  static async urlAlreadyImported (channelId: number, targetUrl: string): Promise<boolean> {
+    const element = await VideoImportModel.unscoped().findOne({
+      where: {
+        targetUrl,
+        state: {
+          [Op.in]: [ VideoImportState.PENDING, VideoImportState.PROCESSING, VideoImportState.SUCCESS ]
+        }
+      },
+      include: [
+        {
+          model: VideoModel,
+          required: true,
+          where: {
+            channelId
+          }
+        }
+      ]
+    })
+
+    return !!element
   }
 
   getTargetIdentifier () {
@@ -164,6 +238,10 @@ export class VideoImportModel extends Model<VideoImportModel> {
       ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) })
       : undefined
 
+    const videoChannelSync = this.VideoChannelSync
+      ? { id: this.VideoChannelSync.id, externalChannelUrl: this.VideoChannelSync.externalChannelUrl }
+      : undefined
+
     return {
       id: this.id,
 
@@ -178,7 +256,8 @@ export class VideoImportModel extends Model<VideoImportModel> {
       error: this.error,
       updatedAt: this.updatedAt.toISOString(),
       createdAt: this.createdAt.toISOString(),
-      video
+      video,
+      videoChannelSync
     }
   }