video-import.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {
  2. AfterUpdate,
  3. AllowNull,
  4. BelongsTo,
  5. Column,
  6. CreatedAt,
  7. DataType,
  8. Default,
  9. DefaultScope,
  10. ForeignKey,
  11. Is,
  12. Model,
  13. Table,
  14. UpdatedAt
  15. } from 'sequelize-typescript'
  16. import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import'
  17. import { VideoImport, VideoImportState } from '../../../shared'
  18. import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
  19. import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
  20. import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
  21. import { UserModel } from '../account/user'
  22. import { getSort, throwIfNotValid } from '../utils'
  23. import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
  24. @DefaultScope(() => ({
  25. include: [
  26. {
  27. model: UserModel.unscoped(),
  28. required: true
  29. },
  30. {
  31. model: VideoModel.scope([
  32. VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
  33. VideoModelScopeNames.WITH_TAGS,
  34. VideoModelScopeNames.WITH_THUMBNAILS
  35. ]),
  36. required: false
  37. }
  38. ]
  39. }))
  40. @Table({
  41. tableName: 'videoImport',
  42. indexes: [
  43. {
  44. fields: [ 'videoId' ],
  45. unique: true
  46. },
  47. {
  48. fields: [ 'userId' ]
  49. }
  50. ]
  51. })
  52. export class VideoImportModel extends Model {
  53. @CreatedAt
  54. createdAt: Date
  55. @UpdatedAt
  56. updatedAt: Date
  57. @AllowNull(true)
  58. @Default(null)
  59. @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl', true))
  60. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max))
  61. targetUrl: string
  62. @AllowNull(true)
  63. @Default(null)
  64. @Is('VideoImportMagnetUri', value => throwIfNotValid(value, isVideoMagnetUriValid, 'magnetUri', true))
  65. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) // Use the same constraints than URLs
  66. magnetUri: string
  67. @AllowNull(true)
  68. @Default(null)
  69. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_NAME.max))
  70. torrentName: string
  71. @AllowNull(false)
  72. @Default(null)
  73. @Is('VideoImportState', value => throwIfNotValid(value, isVideoImportStateValid, 'state'))
  74. @Column
  75. state: VideoImportState
  76. @AllowNull(true)
  77. @Default(null)
  78. @Column(DataType.TEXT)
  79. error: string
  80. @ForeignKey(() => UserModel)
  81. @Column
  82. userId: number
  83. @BelongsTo(() => UserModel, {
  84. foreignKey: {
  85. allowNull: false
  86. },
  87. onDelete: 'cascade'
  88. })
  89. User: UserModel
  90. @ForeignKey(() => VideoModel)
  91. @Column
  92. videoId: number
  93. @BelongsTo(() => VideoModel, {
  94. foreignKey: {
  95. allowNull: true
  96. },
  97. onDelete: 'set null'
  98. })
  99. Video: VideoModel
  100. @AfterUpdate
  101. static deleteVideoIfFailed (instance: VideoImportModel, options) {
  102. if (instance.state === VideoImportState.FAILED) {
  103. return instance.Video.destroy({ transaction: options.transaction })
  104. }
  105. return undefined
  106. }
  107. static loadAndPopulateVideo (id: number): Promise<MVideoImportDefault> {
  108. return VideoImportModel.findByPk(id)
  109. }
  110. static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) {
  111. const query = {
  112. distinct: true,
  113. include: [
  114. {
  115. attributes: [ 'id' ],
  116. model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
  117. required: true
  118. }
  119. ],
  120. offset: start,
  121. limit: count,
  122. order: getSort(sort),
  123. where: {
  124. userId
  125. }
  126. }
  127. return VideoImportModel.findAndCountAll<MVideoImportDefault>(query)
  128. .then(({ rows, count }) => {
  129. return {
  130. data: rows,
  131. total: count
  132. }
  133. })
  134. }
  135. getTargetIdentifier () {
  136. return this.targetUrl || this.magnetUri || this.torrentName
  137. }
  138. toFormattedJSON (this: MVideoImportFormattable): VideoImport {
  139. const videoFormatOptions = {
  140. completeDescription: true,
  141. additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
  142. }
  143. const video = this.Video
  144. ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) })
  145. : undefined
  146. return {
  147. id: this.id,
  148. targetUrl: this.targetUrl,
  149. magnetUri: this.magnetUri,
  150. torrentName: this.torrentName,
  151. state: {
  152. id: this.state,
  153. label: VideoImportModel.getStateLabel(this.state)
  154. },
  155. error: this.error,
  156. updatedAt: this.updatedAt.toISOString(),
  157. createdAt: this.createdAt.toISOString(),
  158. video
  159. }
  160. }
  161. private static getStateLabel (id: number) {
  162. return VIDEO_IMPORT_STATES[id] || 'Unknown'
  163. }
  164. }