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