]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-channel.ts
Add ability to set a name to a channel
[github/Chocobozzz/PeerTube.git] / server / models / video / video-channel.ts
1 import {
2 AllowNull,
3 BeforeDestroy,
4 BelongsTo,
5 Column,
6 CreatedAt,
7 DataType,
8 Default,
9 DefaultScope,
10 ForeignKey,
11 HasMany,
12 Is,
13 Model,
14 Scopes,
15 Table,
16 UpdatedAt
17 } from 'sequelize-typescript'
18 import { ActivityPubActor } from '../../../shared/models/activitypub'
19 import { VideoChannel } from '../../../shared/models/videos'
20 import {
21 isVideoChannelDescriptionValid,
22 isVideoChannelNameValid,
23 isVideoChannelSupportValid
24 } from '../../helpers/custom-validators/video-channels'
25 import { sendDeleteActor } from '../../lib/activitypub/send'
26 import { AccountModel } from '../account/account'
27 import { ActorModel } from '../activitypub/actor'
28 import { getSort, throwIfNotValid } from '../utils'
29 import { VideoModel } from './video'
30 import { CONSTRAINTS_FIELDS } from '../../initializers'
31 import { AvatarModel } from '../avatar/avatar'
32 import { ServerModel } from '../server/server'
33
34 enum ScopeNames {
35 WITH_ACCOUNT = 'WITH_ACCOUNT',
36 WITH_ACTOR = 'WITH_ACTOR',
37 WITH_VIDEOS = 'WITH_VIDEOS'
38 }
39
40 @DefaultScope({
41 include: [
42 {
43 model: () => ActorModel,
44 required: true
45 }
46 ]
47 })
48 @Scopes({
49 [ScopeNames.WITH_ACCOUNT]: {
50 include: [
51 {
52 model: () => AccountModel.unscoped(),
53 required: true,
54 include: [
55 {
56 model: () => ActorModel.unscoped(),
57 required: true,
58 include: [
59 {
60 model: () => AvatarModel.unscoped(),
61 required: false
62 }
63 ]
64 }
65 ]
66 }
67 ]
68 },
69 [ScopeNames.WITH_VIDEOS]: {
70 include: [
71 () => VideoModel
72 ]
73 },
74 [ScopeNames.WITH_ACTOR]: {
75 include: [
76 () => ActorModel
77 ]
78 }
79 })
80 @Table({
81 tableName: 'videoChannel',
82 indexes: [
83 {
84 fields: [ 'accountId' ]
85 },
86 {
87 fields: [ 'actorId' ]
88 }
89 ]
90 })
91 export class VideoChannelModel extends Model<VideoChannelModel> {
92
93 @AllowNull(false)
94 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
95 @Column
96 name: string
97
98 @AllowNull(true)
99 @Default(null)
100 @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description'))
101 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.DESCRIPTION.max))
102 description: string
103
104 @AllowNull(true)
105 @Default(null)
106 @Is('VideoChannelSupport', value => throwIfNotValid(value, isVideoChannelSupportValid, 'support'))
107 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.SUPPORT.max))
108 support: string
109
110 @CreatedAt
111 createdAt: Date
112
113 @UpdatedAt
114 updatedAt: Date
115
116 @ForeignKey(() => ActorModel)
117 @Column
118 actorId: number
119
120 @BelongsTo(() => ActorModel, {
121 foreignKey: {
122 allowNull: false
123 },
124 onDelete: 'cascade'
125 })
126 Actor: ActorModel
127
128 @ForeignKey(() => AccountModel)
129 @Column
130 accountId: number
131
132 @BelongsTo(() => AccountModel, {
133 foreignKey: {
134 allowNull: false
135 },
136 hooks: true
137 })
138 Account: AccountModel
139
140 @HasMany(() => VideoModel, {
141 foreignKey: {
142 name: 'channelId',
143 allowNull: false
144 },
145 onDelete: 'CASCADE',
146 hooks: true
147 })
148 Videos: VideoModel[]
149
150 @BeforeDestroy
151 static async sendDeleteIfOwned (instance: VideoChannelModel, options) {
152 if (!instance.Actor) {
153 instance.Actor = await instance.$get('Actor', { transaction: options.transaction }) as ActorModel
154 }
155
156 if (instance.Actor.isOwned()) {
157 return sendDeleteActor(instance.Actor, options.transaction)
158 }
159
160 return undefined
161 }
162
163 static countByAccount (accountId: number) {
164 const query = {
165 where: {
166 accountId
167 }
168 }
169
170 return VideoChannelModel.count(query)
171 }
172
173 static listForApi (start: number, count: number, sort: string) {
174 const query = {
175 offset: start,
176 limit: count,
177 order: getSort(sort)
178 }
179
180 return VideoChannelModel
181 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
182 .findAndCountAll(query)
183 .then(({ rows, count }) => {
184 return { total: count, data: rows }
185 })
186 }
187
188 static listByAccount (accountId: number) {
189 const query = {
190 order: getSort('createdAt'),
191 include: [
192 {
193 model: AccountModel,
194 where: {
195 id: accountId
196 },
197 required: true
198 }
199 ]
200 }
201
202 return VideoChannelModel
203 .findAndCountAll(query)
204 .then(({ rows, count }) => {
205 return { total: count, data: rows }
206 })
207 }
208
209 static loadByIdAndAccount (id: number, accountId: number) {
210 const query = {
211 where: {
212 id,
213 accountId
214 }
215 }
216
217 return VideoChannelModel
218 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
219 .findOne(query)
220 }
221
222 static loadAndPopulateAccount (id: number) {
223 return VideoChannelModel
224 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
225 .findById(id)
226 }
227
228 static loadByUUIDAndPopulateAccount (uuid: string) {
229 const query = {
230 include: [
231 {
232 model: ActorModel,
233 required: true,
234 where: {
235 uuid
236 }
237 }
238 ]
239 }
240
241 return VideoChannelModel
242 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
243 .findOne(query)
244 }
245
246 static loadLocalByNameAndPopulateAccount (name: string) {
247 const query = {
248 include: [
249 {
250 model: ActorModel,
251 required: true,
252 where: {
253 preferredUsername: name,
254 serverId: null
255 }
256 }
257 ]
258 }
259
260 return VideoChannelModel
261 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
262 .findOne(query)
263 }
264
265 static loadByNameAndHostAndPopulateAccount (name: string, host: string) {
266 const query = {
267 include: [
268 {
269 model: ActorModel,
270 required: true,
271 where: {
272 preferredUsername: name
273 },
274 include: [
275 {
276 model: ServerModel,
277 required: true,
278 where: { host }
279 }
280 ]
281 }
282 ]
283 }
284
285 return VideoChannelModel
286 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
287 .findOne(query)
288 }
289
290 static loadAndPopulateAccountAndVideos (id: number) {
291 const options = {
292 include: [
293 VideoModel
294 ]
295 }
296
297 return VideoChannelModel
298 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
299 .findById(id, options)
300 }
301
302 toFormattedJSON (): VideoChannel {
303 const actor = this.Actor.toFormattedJSON()
304 const videoChannel = {
305 id: this.id,
306 displayName: this.getDisplayName(),
307 description: this.description,
308 support: this.support,
309 isLocal: this.Actor.isOwned(),
310 createdAt: this.createdAt,
311 updatedAt: this.updatedAt,
312 ownerAccount: undefined
313 }
314
315 if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON()
316
317 return Object.assign(actor, videoChannel)
318 }
319
320 toActivityPubObject (): ActivityPubActor {
321 const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
322
323 return Object.assign(obj, {
324 summary: this.description,
325 support: this.support,
326 attributedTo: [
327 {
328 type: 'Person' as 'Person',
329 id: this.Account.Actor.url
330 }
331 ]
332 })
333 }
334
335 getDisplayName () {
336 return this.name
337 }
338 }