e5d39582bdf7698f9b36e3adf1a5e7764b054be5
[oweals/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 } from '../utils'
11 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
12
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
40   @AllowNull(false)
41   @Column(DataType.ENUM(values(VIDEO_RATE_TYPES)))
42   type: VideoRateType
43
44   @AllowNull(false)
45   @Is('AccountVideoRateUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
46   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_RATES.URL.max))
47   url: string
48
49   @CreatedAt
50   createdAt: Date
51
52   @UpdatedAt
53   updatedAt: Date
54
55   @ForeignKey(() => VideoModel)
56   @Column
57   videoId: number
58
59   @BelongsTo(() => VideoModel, {
60     foreignKey: {
61       allowNull: false
62     },
63     onDelete: 'CASCADE'
64   })
65   Video: VideoModel
66
67   @ForeignKey(() => AccountModel)
68   @Column
69   accountId: number
70
71   @BelongsTo(() => AccountModel, {
72     foreignKey: {
73       allowNull: false
74     },
75     onDelete: 'CASCADE'
76   })
77   Account: AccountModel
78
79   static load (accountId: number, videoId: number, transaction?: Transaction) {
80     const options: IFindOptions<AccountVideoRateModel> = {
81       where: {
82         accountId,
83         videoId
84       }
85     }
86     if (transaction) options.transaction = transaction
87
88     return AccountVideoRateModel.findOne(options)
89   }
90
91   static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) {
92     const options: IFindOptions<AccountVideoRateModel> = {
93       where: {
94         videoId,
95         type: rateType
96       },
97       include: [
98         {
99           model: AccountModel.unscoped(),
100           required: true,
101           include: [
102             {
103               attributes: [ 'id', 'url', 'preferredUsername' ],
104               model: ActorModel.unscoped(),
105               required: true,
106               where: {
107                 preferredUsername: accountName
108               }
109             }
110           ]
111         },
112         {
113           model: VideoModel.unscoped(),
114           required: true
115         }
116       ]
117     }
118     if (transaction) options.transaction = transaction
119
120     return AccountVideoRateModel.findOne(options)
121   }
122
123   static loadByUrl (url: string, transaction: Transaction) {
124     const options: IFindOptions<AccountVideoRateModel> = {
125       where: {
126         url
127       }
128     }
129     if (transaction) options.transaction = transaction
130
131     return AccountVideoRateModel.findOne(options)
132   }
133
134   static listAndCountAccountUrlsByVideoId (rateType: VideoRateType, videoId: number, start: number, count: number, t?: Transaction) {
135     const query = {
136       offset: start,
137       limit: count,
138       where: {
139         videoId,
140         type: rateType
141       },
142       transaction: t,
143       include: [
144         {
145           attributes: [ 'actorId' ],
146           model: AccountModel.unscoped(),
147           required: true,
148           include: [
149             {
150               attributes: [ 'url' ],
151               model: ActorModel.unscoped(),
152               required: true
153             }
154           ]
155         }
156       ]
157     }
158
159     return AccountVideoRateModel.findAndCountAll(query)
160   }
161
162   static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
163     return AccountVideoRateModel.sequelize.transaction(async t => {
164       const query = {
165         where: {
166           updatedAt: {
167             [Op.lt]: beforeUpdatedAt
168           },
169           videoId,
170           type
171         },
172         transaction: t
173       }
174
175       const deleted = await AccountVideoRateModel.destroy(query)
176
177       const options = {
178         transaction: t,
179         where: {
180           id: videoId
181         }
182       }
183
184       if (type === 'like') await VideoModel.increment({ likes: -deleted }, options)
185       else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options)
186     })
187   }
188 }