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