video-share.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { literal, Op, Transaction } from 'sequelize'
  2. import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
  3. import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
  4. import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
  5. import { MActorDefault } from '../../types/models'
  6. import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
  7. import { ActorModel } from '../activitypub/actor'
  8. import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
  9. import { VideoModel } from './video'
  10. enum ScopeNames {
  11. FULL = 'FULL',
  12. WITH_ACTOR = 'WITH_ACTOR'
  13. }
  14. @Scopes(() => ({
  15. [ScopeNames.FULL]: {
  16. include: [
  17. {
  18. model: ActorModel,
  19. required: true
  20. },
  21. {
  22. model: VideoModel,
  23. required: true
  24. }
  25. ]
  26. },
  27. [ScopeNames.WITH_ACTOR]: {
  28. include: [
  29. {
  30. model: ActorModel,
  31. required: true
  32. }
  33. ]
  34. }
  35. }))
  36. @Table({
  37. tableName: 'videoShare',
  38. indexes: [
  39. {
  40. fields: [ 'actorId' ]
  41. },
  42. {
  43. fields: [ 'videoId' ]
  44. },
  45. {
  46. fields: [ 'url' ],
  47. unique: true
  48. }
  49. ]
  50. })
  51. export class VideoShareModel extends Model {
  52. @AllowNull(false)
  53. @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
  54. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_SHARE.URL.max))
  55. url: string
  56. @CreatedAt
  57. createdAt: Date
  58. @UpdatedAt
  59. updatedAt: Date
  60. @ForeignKey(() => ActorModel)
  61. @Column
  62. actorId: number
  63. @BelongsTo(() => ActorModel, {
  64. foreignKey: {
  65. allowNull: false
  66. },
  67. onDelete: 'cascade'
  68. })
  69. Actor: ActorModel
  70. @ForeignKey(() => VideoModel)
  71. @Column
  72. videoId: number
  73. @BelongsTo(() => VideoModel, {
  74. foreignKey: {
  75. allowNull: false
  76. },
  77. onDelete: 'cascade'
  78. })
  79. Video: VideoModel
  80. static load (actorId: number | string, videoId: number | string, t?: Transaction): Promise<MVideoShareActor> {
  81. return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
  82. where: {
  83. actorId,
  84. videoId
  85. },
  86. transaction: t
  87. })
  88. }
  89. static loadByUrl (url: string, t: Transaction): Promise<MVideoShareFull> {
  90. return VideoShareModel.scope(ScopeNames.FULL).findOne({
  91. where: {
  92. url
  93. },
  94. transaction: t
  95. })
  96. }
  97. static loadActorsByShare (videoId: number, t: Transaction): Promise<MActorDefault[]> {
  98. const query = {
  99. where: {
  100. videoId
  101. },
  102. include: [
  103. {
  104. model: ActorModel,
  105. required: true
  106. }
  107. ],
  108. transaction: t
  109. }
  110. return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
  111. .then((res: MVideoShareFull[]) => res.map(r => r.Actor))
  112. }
  113. static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> {
  114. const safeOwnerId = parseInt(actorOwnerId + '', 10)
  115. // /!\ On actor model
  116. const query = {
  117. where: {
  118. [Op.and]: [
  119. literal(
  120. `EXISTS (` +
  121. ` SELECT 1 FROM "videoShare" ` +
  122. ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
  123. ` INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ` +
  124. ` INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ` +
  125. ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "account"."actorId" = ${safeOwnerId} ` +
  126. ` LIMIT 1` +
  127. `)`
  128. )
  129. ]
  130. },
  131. transaction: t
  132. }
  133. return ActorModel.findAll(query)
  134. }
  135. static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Promise<MActorDefault[]> {
  136. const safeChannelId = parseInt(videoChannelId + '', 10)
  137. // /!\ On actor model
  138. const query = {
  139. where: {
  140. [Op.and]: [
  141. literal(
  142. `EXISTS (` +
  143. ` SELECT 1 FROM "videoShare" ` +
  144. ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
  145. ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "video"."channelId" = ${safeChannelId} ` +
  146. ` LIMIT 1` +
  147. `)`
  148. )
  149. ]
  150. },
  151. transaction: t
  152. }
  153. return ActorModel.findAll(query)
  154. }
  155. static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) {
  156. const query = {
  157. offset: start,
  158. limit: count,
  159. where: {
  160. videoId
  161. },
  162. transaction: t
  163. }
  164. return VideoShareModel.findAndCountAll(query)
  165. }
  166. static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) {
  167. const query = {
  168. where: {
  169. updatedAt: {
  170. [Op.lt]: beforeUpdatedAt
  171. },
  172. videoId,
  173. actorId: {
  174. [Op.notIn]: buildLocalActorIdsIn()
  175. }
  176. }
  177. }
  178. return VideoShareModel.destroy(query)
  179. }
  180. }