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