Federate video abuses
[oweals/peertube.git] / server / models / video / video-channel.ts
1 import * as Sequelize from 'sequelize'
2
3 import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers'
4
5 import { addMethodsToModel, getSort } from '../utils'
6 import {
7   VideoChannelInstance,
8   VideoChannelAttributes,
9
10   VideoChannelMethods
11 } from './video-channel-interface'
12 import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request'
13 import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels'
14 import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
15
16 let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
17 let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
18 let toActivityPubObject: VideoChannelMethods.ToActivityPubObject
19 let isOwned: VideoChannelMethods.IsOwned
20 let countByAccount: VideoChannelMethods.CountByAccount
21 let listOwned: VideoChannelMethods.ListOwned
22 let listForApi: VideoChannelMethods.ListForApi
23 let listByAccount: VideoChannelMethods.ListByAccount
24 let loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount
25 let loadByUUID: VideoChannelMethods.LoadByUUID
26 let loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount
27 let loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount
28 let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID
29 let loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos
30 let loadByUrl: VideoChannelMethods.LoadByUrl
31 let loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl
32
33 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
34   VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel',
35     {
36       uuid: {
37         type: DataTypes.UUID,
38         defaultValue: DataTypes.UUIDV4,
39         allowNull: false,
40         validate: {
41           isUUID: 4
42         }
43       },
44       name: {
45         type: DataTypes.STRING,
46         allowNull: false,
47         validate: {
48           nameValid: value => {
49             const res = isVideoChannelNameValid(value)
50             if (res === false) throw new Error('Video channel name is not valid.')
51           }
52         }
53       },
54       description: {
55         type: DataTypes.STRING,
56         allowNull: true,
57         validate: {
58           descriptionValid: value => {
59             const res = isVideoChannelDescriptionValid(value)
60             if (res === false) throw new Error('Video channel description is not valid.')
61           }
62         }
63       },
64       remote: {
65         type: DataTypes.BOOLEAN,
66         allowNull: false,
67         defaultValue: false
68       },
69       url: {
70         type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max),
71         allowNull: false,
72         validate: {
73           urlValid: value => {
74             const res = isVideoChannelUrlValid(value)
75             if (res === false) throw new Error('Video channel URL is not valid.')
76           }
77         }
78       }
79     },
80     {
81       indexes: [
82         {
83           fields: [ 'accountId' ]
84         }
85       ],
86       hooks: {
87         afterDestroy
88       }
89     }
90   )
91
92   const classMethods = [
93     associate,
94
95     listForApi,
96     listByAccount,
97     listOwned,
98     loadByIdAndAccount,
99     loadAndPopulateAccount,
100     loadByUUIDAndPopulateAccount,
101     loadByUUID,
102     loadByHostAndUUID,
103     loadAndPopulateAccountAndVideos,
104     countByAccount,
105     loadByUrl,
106     loadByUUIDOrUrl
107   ]
108   const instanceMethods = [
109     isOwned,
110     toFormattedJSON,
111     toActivityPubObject
112   ]
113   addMethodsToModel(VideoChannel, classMethods, instanceMethods)
114
115   return VideoChannel
116 }
117
118 // ------------------------------ METHODS ------------------------------
119
120 isOwned = function (this: VideoChannelInstance) {
121   return this.remote === false
122 }
123
124 toFormattedJSON = function (this: VideoChannelInstance) {
125   const json = {
126     id: this.id,
127     uuid: this.uuid,
128     name: this.name,
129     description: this.description,
130     isLocal: this.isOwned(),
131     createdAt: this.createdAt,
132     updatedAt: this.updatedAt
133   }
134
135   if (this.Account !== undefined) {
136     json['owner'] = {
137       name: this.Account.name,
138       uuid: this.Account.uuid
139     }
140   }
141
142   if (Array.isArray(this.Videos)) {
143     json['videos'] = this.Videos.map(v => v.toFormattedJSON())
144   }
145
146   return json
147 }
148
149 toActivityPubObject = function (this: VideoChannelInstance) {
150   const json = {
151     type: 'VideoChannel' as 'VideoChannel',
152     id: this.url,
153     uuid: this.uuid,
154     content: this.description,
155     name: this.name,
156     published: this.createdAt.toISOString(),
157     updated: this.updatedAt.toISOString()
158   }
159
160   return json
161 }
162
163 // ------------------------------ STATICS ------------------------------
164
165 function associate (models) {
166   VideoChannel.belongsTo(models.Account, {
167     foreignKey: {
168       name: 'accountId',
169       allowNull: false
170     },
171     onDelete: 'CASCADE'
172   })
173
174   VideoChannel.hasMany(models.Video, {
175     foreignKey: {
176       name: 'channelId',
177       allowNull: false
178     },
179     onDelete: 'CASCADE'
180   })
181 }
182
183 function afterDestroy (videoChannel: VideoChannelInstance) {
184   if (videoChannel.isOwned()) {
185     return sendDeleteVideoChannel(videoChannel, undefined)
186   }
187
188   return undefined
189 }
190
191 countByAccount = function (accountId: number) {
192   const query = {
193     where: {
194       accountId
195     }
196   }
197
198   return VideoChannel.count(query)
199 }
200
201 listOwned = function () {
202   const query = {
203     where: {
204       remote: false
205     },
206     include: [ VideoChannel['sequelize'].models.Account ]
207   }
208
209   return VideoChannel.findAll(query)
210 }
211
212 listForApi = function (start: number, count: number, sort: string) {
213   const query = {
214     offset: start,
215     limit: count,
216     order: [ getSort(sort) ],
217     include: [
218       {
219         model: VideoChannel['sequelize'].models.Account,
220         required: true,
221         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
222       }
223     ]
224   }
225
226   return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
227     return { total: count, data: rows }
228   })
229 }
230
231 listByAccount = function (accountId: number) {
232   const query = {
233     order: [ getSort('createdAt') ],
234     include: [
235       {
236         model: VideoChannel['sequelize'].models.Account,
237         where: {
238           id: accountId
239         },
240         required: true,
241         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
242       }
243     ]
244   }
245
246   return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
247     return { total: count, data: rows }
248   })
249 }
250
251 loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
252   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
253     where: {
254       uuid
255     }
256   }
257
258   if (t !== undefined) query.transaction = t
259
260   return VideoChannel.findOne(query)
261 }
262
263 loadByUrl = function (url: string, t?: Sequelize.Transaction) {
264   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
265     where: {
266       url
267     },
268     include: [ VideoChannel['sequelize'].models.Account ]
269   }
270
271   if (t !== undefined) query.transaction = t
272
273   return VideoChannel.findOne(query)
274 }
275
276 loadByUUIDOrUrl = function (uuid: string, url: string, t?: Sequelize.Transaction) {
277   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
278     where: {
279       [Sequelize.Op.or]: [
280         { uuid },
281         { url }
282       ]
283     }
284   }
285
286   if (t !== undefined) query.transaction = t
287
288   return VideoChannel.findOne(query)
289 }
290
291 loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
292   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
293     where: {
294       uuid
295     },
296     include: [
297       {
298         model: VideoChannel['sequelize'].models.Account,
299         include: [
300           {
301             model: VideoChannel['sequelize'].models.Server,
302             required: true,
303             where: {
304               host: fromHost
305             }
306           }
307         ]
308       }
309     ]
310   }
311
312   if (t !== undefined) query.transaction = t
313
314   return VideoChannel.findOne(query)
315 }
316
317 loadByIdAndAccount = function (id: number, accountId: number) {
318   const options = {
319     where: {
320       id,
321       accountId
322     },
323     include: [
324       {
325         model: VideoChannel['sequelize'].models.Account,
326         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
327       }
328     ]
329   }
330
331   return VideoChannel.findOne(options)
332 }
333
334 loadAndPopulateAccount = function (id: number) {
335   const options = {
336     include: [
337       {
338         model: VideoChannel['sequelize'].models.Account,
339         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
340       }
341     ]
342   }
343
344   return VideoChannel.findById(id, options)
345 }
346
347 loadByUUIDAndPopulateAccount = function (uuid: string) {
348   const options = {
349     where: {
350       uuid
351     },
352     include: [
353       {
354         model: VideoChannel['sequelize'].models.Account,
355         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
356       }
357     ]
358   }
359
360   return VideoChannel.findOne(options)
361 }
362
363 loadAndPopulateAccountAndVideos = function (id: number) {
364   const options = {
365     include: [
366       {
367         model: VideoChannel['sequelize'].models.Account,
368         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
369       },
370       VideoChannel['sequelize'].models.Video
371     ]
372   }
373
374   return VideoChannel.findById(id, options)
375 }