video-import.ts 4.4 KB

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