ニジカ投稿局 https://tv.nizika.tv
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

216 lines
5.7 KiB

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