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