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