ニジカ投稿局 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.
 
 
 
 
 

281 lines
7.2 KiB

  1. import { AccountVideoRate, type VideoRateType } from '@peertube/peertube-models'
  2. import {
  3. MAccountVideoRate,
  4. MAccountVideoRateAccountUrl,
  5. MAccountVideoRateAccountVideo,
  6. MAccountVideoRateFormattable,
  7. MAccountVideoRateVideoUrl
  8. } from '@server/types/models/index.js'
  9. import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize'
  10. import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
  11. import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
  12. import { CONSTRAINTS_FIELDS, USER_EXPORT_MAX_ITEMS, VIDEO_RATE_TYPES } from '../../initializers/constants.js'
  13. import { ActorModel } from '../actor/actor.js'
  14. import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
  15. import { SummaryOptions, VideoChannelModel, ScopeNames as VideoChannelScopeNames } from '../video/video-channel.js'
  16. import { VideoModel } from '../video/video.js'
  17. import { AccountModel } from './account.js'
  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 SequelizeModel<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: Awaited<VideoModel>
  65. @ForeignKey(() => AccountModel)
  66. @Column
  67. accountId: number
  68. @BelongsTo(() => AccountModel, {
  69. foreignKey: {
  70. allowNull: false
  71. },
  72. onDelete: 'CASCADE'
  73. })
  74. Account: Awaited<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 loadLocalAndPopulateVideo (
  103. rateType: VideoRateType,
  104. accountName: string,
  105. videoId: number,
  106. t?: Transaction
  107. ): Promise<MAccountVideoRateAccountVideo> {
  108. const options: FindOptions = {
  109. where: {
  110. videoId,
  111. type: rateType
  112. },
  113. include: [
  114. {
  115. model: AccountModel.unscoped(),
  116. required: true,
  117. include: [
  118. {
  119. attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ],
  120. model: ActorModel.unscoped(),
  121. required: true,
  122. where: {
  123. [Op.and]: [
  124. ActorModel.wherePreferredUsername(accountName),
  125. { serverId: null }
  126. ]
  127. }
  128. }
  129. ]
  130. },
  131. {
  132. model: VideoModel.unscoped(),
  133. required: true
  134. }
  135. ]
  136. }
  137. if (t) options.transaction = t
  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. // ---------------------------------------------------------------------------
  150. static listByAccountForApi (options: {
  151. start: number
  152. count: number
  153. sort: string
  154. type?: string
  155. accountId: number
  156. }) {
  157. const getQuery = (forCount: boolean) => {
  158. const query: FindOptions = {
  159. offset: options.start,
  160. limit: options.count,
  161. order: getSort(options.sort),
  162. where: {
  163. accountId: options.accountId
  164. }
  165. }
  166. if (options.type) query.where['type'] = options.type
  167. if (forCount !== true) {
  168. query.include = [
  169. {
  170. model: VideoModel,
  171. required: true,
  172. include: [
  173. {
  174. model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
  175. required: true
  176. }
  177. ]
  178. }
  179. ]
  180. }
  181. return query
  182. }
  183. return Promise.all([
  184. AccountVideoRateModel.count(getQuery(true)),
  185. AccountVideoRateModel.findAll(getQuery(false))
  186. ]).then(([ total, data ]) => ({ total, data }))
  187. }
  188. static listRemoteRateUrlsOfLocalVideos () {
  189. const query = `SELECT "accountVideoRate".url FROM "accountVideoRate" ` +
  190. `INNER JOIN account ON account.id = "accountVideoRate"."accountId" ` +
  191. `INNER JOIN actor ON actor.id = account."actorId" AND actor."serverId" IS NOT NULL ` +
  192. `INNER JOIN video ON video.id = "accountVideoRate"."videoId" AND video.remote IS FALSE`
  193. return AccountVideoRateModel.sequelize.query<{ url: string }>(query, {
  194. type: QueryTypes.SELECT,
  195. raw: true
  196. }).then(rows => rows.map(r => r.url))
  197. }
  198. static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
  199. const query = {
  200. offset: start,
  201. limit: count,
  202. where: {
  203. videoId,
  204. type: rateType
  205. },
  206. transaction: t,
  207. include: [
  208. {
  209. attributes: [ 'actorId' ],
  210. model: AccountModel.unscoped(),
  211. required: true,
  212. include: [
  213. {
  214. attributes: [ 'url' ],
  215. model: ActorModel.unscoped(),
  216. required: true
  217. }
  218. ]
  219. }
  220. ]
  221. }
  222. return Promise.all([
  223. AccountVideoRateModel.count(query),
  224. AccountVideoRateModel.findAll<MAccountVideoRateAccountUrl>(query)
  225. ]).then(([ total, data ]) => ({ total, data }))
  226. }
  227. static listRatesOfAccountIdForExport (accountId: number, rateType: VideoRateType): Promise<MAccountVideoRateVideoUrl[]> {
  228. return AccountVideoRateModel.findAll({
  229. where: {
  230. accountId,
  231. type: rateType
  232. },
  233. include: [
  234. {
  235. attributes: [ 'url' ],
  236. model: VideoModel,
  237. required: true
  238. }
  239. ],
  240. limit: USER_EXPORT_MAX_ITEMS
  241. })
  242. }
  243. // ---------------------------------------------------------------------------
  244. toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate {
  245. return {
  246. video: this.Video.toFormattedJSON(),
  247. rating: this.type
  248. }
  249. }
  250. }