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