Handle announces in inbox
[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,
157     updated: this.updatedAt
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   }
269
270   if (t !== undefined) query.transaction = t
271
272   return VideoChannel.findOne(query)
273 }
274
275 loadByUUIDOrUrl = function (uuid: string, url: string, t?: Sequelize.Transaction) {
276   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
277     where: {
278       [Sequelize.Op.or]: [
279         { uuid },
280         { url }
281       ]
282     }
283   }
284
285   if (t !== undefined) query.transaction = t
286
287   return VideoChannel.findOne(query)
288 }
289
290 loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
291   const query: Sequelize.FindOptions<VideoChannelAttributes> = {
292     where: {
293       uuid
294     },
295     include: [
296       {
297         model: VideoChannel['sequelize'].models.Account,
298         include: [
299           {
300             model: VideoChannel['sequelize'].models.Server,
301             required: true,
302             where: {
303               host: fromHost
304             }
305           }
306         ]
307       }
308     ]
309   }
310
311   if (t !== undefined) query.transaction = t
312
313   return VideoChannel.findOne(query)
314 }
315
316 loadByIdAndAccount = function (id: number, accountId: number) {
317   const options = {
318     where: {
319       id,
320       accountId
321     },
322     include: [
323       {
324         model: VideoChannel['sequelize'].models.Account,
325         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
326       }
327     ]
328   }
329
330   return VideoChannel.findOne(options)
331 }
332
333 loadAndPopulateAccount = function (id: number) {
334   const options = {
335     include: [
336       {
337         model: VideoChannel['sequelize'].models.Account,
338         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
339       }
340     ]
341   }
342
343   return VideoChannel.findById(id, options)
344 }
345
346 loadByUUIDAndPopulateAccount = function (uuid: string) {
347   const options = {
348     where: {
349       uuid
350     },
351     include: [
352       {
353         model: VideoChannel['sequelize'].models.Account,
354         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
355       }
356     ]
357   }
358
359   return VideoChannel.findOne(options)
360 }
361
362 loadAndPopulateAccountAndVideos = function (id: number) {
363   const options = {
364     include: [
365       {
366         model: VideoChannel['sequelize'].models.Account,
367         include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
368       },
369       VideoChannel['sequelize'].models.Video
370     ]
371   }
372
373   return VideoChannel.findById(id, options)
374 }