account-video-rate.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize'
  2. import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
  3. import {
  4. MAccountVideoRate,
  5. MAccountVideoRateAccountUrl,
  6. MAccountVideoRateAccountVideo,
  7. MAccountVideoRateFormattable
  8. } from '@server/types/models'
  9. import { AccountVideoRate, VideoRateType } from '@shared/models'
  10. import { AttributesOnly } from '@shared/typescript-utils'
  11. import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
  12. import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
  13. import { ActorModel } from '../actor/actor'
  14. import { getSort, throwIfNotValid } from '../utils'
  15. import { VideoModel } from '../video/video'
  16. import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
  17. import { AccountModel } from './account'
  18. /*
  19. Account rates per video.
  20. */
  21. @Table({
  22. tableName: 'accountVideoRate',
  23. indexes: [
  24. {
  25. fields: [ 'videoId', 'accountId' ],
  26. unique: true
  27. },
  28. {
  29. fields: [ 'videoId' ]
  30. },
  31. {
  32. fields: [ 'accountId' ]
  33. },
  34. {
  35. fields: [ 'videoId', 'type' ]
  36. },
  37. {
  38. fields: [ 'url' ],
  39. unique: true
  40. }
  41. ]
  42. })
  43. export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountVideoRateModel>>> {
  44. @AllowNull(false)
  45. @Column(DataType.ENUM(...Object.values(VIDEO_RATE_TYPES)))
  46. type: VideoRateType
  47. @AllowNull(false)
  48. @Is('AccountVideoRateUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
  49. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_RATES.URL.max))
  50. url: string
  51. @CreatedAt
  52. createdAt: Date
  53. @UpdatedAt
  54. updatedAt: Date
  55. @ForeignKey(() => VideoModel)
  56. @Column
  57. videoId: number
  58. @BelongsTo(() => VideoModel, {
  59. foreignKey: {
  60. allowNull: false
  61. },
  62. onDelete: 'CASCADE'
  63. })
  64. Video: VideoModel
  65. @ForeignKey(() => AccountModel)
  66. @Column
  67. accountId: number
  68. @BelongsTo(() => AccountModel, {
  69. foreignKey: {
  70. allowNull: false
  71. },
  72. onDelete: 'CASCADE'
  73. })
  74. Account: AccountModel
  75. static load (accountId: number, videoId: number, transaction?: Transaction): Promise<MAccountVideoRate> {
  76. const options: FindOptions = {
  77. where: {
  78. accountId,
  79. videoId
  80. }
  81. }
  82. if (transaction) options.transaction = transaction
  83. return AccountVideoRateModel.findOne(options)
  84. }
  85. static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Promise<MAccountVideoRate> {
  86. const options: FindOptions = {
  87. where: {
  88. [Op.or]: [
  89. {
  90. accountId,
  91. videoId
  92. },
  93. {
  94. url
  95. }
  96. ]
  97. }
  98. }
  99. if (t) options.transaction = t
  100. return AccountVideoRateModel.findOne(options)
  101. }
  102. static listByAccountForApi (options: {
  103. start: number
  104. count: number
  105. sort: string
  106. type?: string
  107. accountId: number
  108. }) {
  109. const getQuery = (forCount: boolean) => {
  110. const query: FindOptions = {
  111. offset: options.start,
  112. limit: options.count,
  113. order: getSort(options.sort),
  114. where: {
  115. accountId: options.accountId
  116. }
  117. }
  118. if (options.type) query.where['type'] = options.type
  119. if (forCount !== true) {
  120. query.include = [
  121. {
  122. model: VideoModel,
  123. required: true,
  124. include: [
  125. {
  126. model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
  127. required: true
  128. }
  129. ]
  130. }
  131. ]
  132. }
  133. return query
  134. }
  135. return Promise.all([
  136. AccountVideoRateModel.count(getQuery(true)),
  137. AccountVideoRateModel.findAll(getQuery(false))
  138. ]).then(([ total, data ]) => ({ total, data }))
  139. }
  140. static listRemoteRateUrlsOfLocalVideos () {
  141. const query = `SELECT "accountVideoRate".url FROM "accountVideoRate" ` +
  142. `INNER JOIN account ON account.id = "accountVideoRate"."accountId" ` +
  143. `INNER JOIN actor ON actor.id = account."actorId" AND actor."serverId" IS NOT NULL ` +
  144. `INNER JOIN video ON video.id = "accountVideoRate"."videoId" AND video.remote IS FALSE`
  145. return AccountVideoRateModel.sequelize.query<{ url: string }>(query, {
  146. type: QueryTypes.SELECT,
  147. raw: true
  148. }).then(rows => rows.map(r => r.url))
  149. }
  150. static loadLocalAndPopulateVideo (
  151. rateType: VideoRateType,
  152. accountName: string,
  153. videoId: number,
  154. t?: Transaction
  155. ): Promise<MAccountVideoRateAccountVideo> {
  156. const options: FindOptions = {
  157. where: {
  158. videoId,
  159. type: rateType
  160. },
  161. include: [
  162. {
  163. model: AccountModel.unscoped(),
  164. required: true,
  165. include: [
  166. {
  167. attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ],
  168. model: ActorModel.unscoped(),
  169. required: true,
  170. where: {
  171. preferredUsername: accountName,
  172. serverId: null
  173. }
  174. }
  175. ]
  176. },
  177. {
  178. model: VideoModel.unscoped(),
  179. required: true
  180. }
  181. ]
  182. }
  183. if (t) options.transaction = t
  184. return AccountVideoRateModel.findOne(options)
  185. }
  186. static loadByUrl (url: string, transaction: Transaction) {
  187. const options: FindOptions = {
  188. where: {
  189. url
  190. }
  191. }
  192. if (transaction) options.transaction = transaction
  193. return AccountVideoRateModel.findOne(options)
  194. }
  195. static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
  196. const query = {
  197. offset: start,
  198. limit: count,
  199. where: {
  200. videoId,
  201. type: rateType
  202. },
  203. transaction: t,
  204. include: [
  205. {
  206. attributes: [ 'actorId' ],
  207. model: AccountModel.unscoped(),
  208. required: true,
  209. include: [
  210. {
  211. attributes: [ 'url' ],
  212. model: ActorModel.unscoped(),
  213. required: true
  214. }
  215. ]
  216. }
  217. ]
  218. }
  219. return Promise.all([
  220. AccountVideoRateModel.count(query),
  221. AccountVideoRateModel.findAll<MAccountVideoRateAccountUrl>(query)
  222. ]).then(([ total, data ]) => ({ total, data }))
  223. }
  224. toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate {
  225. return {
  226. video: this.Video.toFormattedJSON(),
  227. rating: this.type
  228. }
  229. }
  230. }