account-video-rate.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { values } from 'lodash'
  2. import { FindOptions, Op, Transaction } from 'sequelize'
  3. import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
  4. import { VideoRateType } from '../../../shared/models/videos'
  5. import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
  6. import { VideoModel } from '../video/video'
  7. import { AccountModel } from './account'
  8. import { ActorModel } from '../activitypub/actor'
  9. import { getSort, throwIfNotValid } from '../utils'
  10. import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
  11. import { AccountVideoRate } from '../../../shared'
  12. import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from '../video/video-channel'
  13. /*
  14. Account rates per video.
  15. */
  16. @Table({
  17. tableName: 'accountVideoRate',
  18. indexes: [
  19. {
  20. fields: [ 'videoId', 'accountId' ],
  21. unique: true
  22. },
  23. {
  24. fields: [ 'videoId' ]
  25. },
  26. {
  27. fields: [ 'accountId' ]
  28. },
  29. {
  30. fields: [ 'videoId', 'type' ]
  31. },
  32. {
  33. fields: [ 'url' ],
  34. unique: true
  35. }
  36. ]
  37. })
  38. export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
  39. @AllowNull(false)
  40. @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES)))
  41. type: VideoRateType
  42. @AllowNull(false)
  43. @Is('AccountVideoRateUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
  44. @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_RATES.URL.max))
  45. url: string
  46. @CreatedAt
  47. createdAt: Date
  48. @UpdatedAt
  49. updatedAt: Date
  50. @ForeignKey(() => VideoModel)
  51. @Column
  52. videoId: number
  53. @BelongsTo(() => VideoModel, {
  54. foreignKey: {
  55. allowNull: false
  56. },
  57. onDelete: 'CASCADE'
  58. })
  59. Video: VideoModel
  60. @ForeignKey(() => AccountModel)
  61. @Column
  62. accountId: number
  63. @BelongsTo(() => AccountModel, {
  64. foreignKey: {
  65. allowNull: false
  66. },
  67. onDelete: 'CASCADE'
  68. })
  69. Account: AccountModel
  70. static load (accountId: number, videoId: number, transaction?: Transaction) {
  71. const options: FindOptions = {
  72. where: {
  73. accountId,
  74. videoId
  75. }
  76. }
  77. if (transaction) options.transaction = transaction
  78. return AccountVideoRateModel.findOne(options)
  79. }
  80. static listByAccountForApi (options: {
  81. start: number,
  82. count: number,
  83. sort: string,
  84. type?: string,
  85. accountId: number
  86. }) {
  87. const query: FindOptions = {
  88. offset: options.start,
  89. limit: options.count,
  90. order: getSort(options.sort),
  91. where: {
  92. accountId: options.accountId
  93. },
  94. include: [
  95. {
  96. model: VideoModel,
  97. required: true,
  98. include: [
  99. {
  100. model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, true] }),
  101. required: true
  102. }
  103. ]
  104. }
  105. ]
  106. }
  107. if (options.type) query.where['type'] = options.type
  108. return AccountVideoRateModel.findAndCountAll(query)
  109. }
  110. static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) {
  111. const options: FindOptions = {
  112. where: {
  113. videoId,
  114. type: rateType
  115. },
  116. include: [
  117. {
  118. model: AccountModel.unscoped(),
  119. required: true,
  120. include: [
  121. {
  122. attributes: [ 'id', 'url', 'preferredUsername' ],
  123. model: ActorModel.unscoped(),
  124. required: true,
  125. where: {
  126. preferredUsername: accountName
  127. }
  128. }
  129. ]
  130. },
  131. {
  132. model: VideoModel.unscoped(),
  133. required: true
  134. }
  135. ]
  136. }
  137. if (transaction) options.transaction = transaction
  138. return AccountVideoRateModel.findOne(options)
  139. }
  140. static loadByUrl (url: string, transaction: Transaction) {
  141. const options: FindOptions = {
  142. where: {
  143. url
  144. }
  145. }
  146. if (transaction) options.transaction = transaction
  147. return AccountVideoRateModel.findOne(options)
  148. }
  149. static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
  150. const query = {
  151. offset: start,
  152. limit: count,
  153. where: {
  154. videoId,
  155. type: rateType
  156. },
  157. transaction: t,
  158. include: [
  159. {
  160. attributes: [ 'actorId' ],
  161. model: AccountModel.unscoped(),
  162. required: true,
  163. include: [
  164. {
  165. attributes: [ 'url' ],
  166. model: ActorModel.unscoped(),
  167. required: true
  168. }
  169. ]
  170. }
  171. ]
  172. }
  173. return AccountVideoRateModel.findAndCountAll(query)
  174. }
  175. static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
  176. return AccountVideoRateModel.sequelize.transaction(async t => {
  177. const query = {
  178. where: {
  179. updatedAt: {
  180. [Op.lt]: beforeUpdatedAt
  181. },
  182. videoId,
  183. type
  184. },
  185. transaction: t
  186. }
  187. const deleted = await AccountVideoRateModel.destroy(query)
  188. const options = {
  189. transaction: t,
  190. where: {
  191. id: videoId
  192. }
  193. }
  194. if (type === 'like') await VideoModel.increment({ likes: -deleted }, options)
  195. else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options)
  196. })
  197. }
  198. toFormattedJSON (): AccountVideoRate {
  199. return {
  200. video: this.Video.toFormattedJSON(),
  201. rating: this.type
  202. }
  203. }
  204. }