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