]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-channel.ts
068c8029d033eb0cec79c446495bc42fa63ae7e2
[github/Chocobozzz/PeerTube.git] / server / models / video / video-channel.ts
1 import * as Sequelize from 'sequelize'
2 import {
3 AfterDestroy,
4 AllowNull,
5 BelongsTo,
6 Column,
7 CreatedAt,
8 DataType,
9 Default,
10 ForeignKey,
11 HasMany,
12 Is,
13 IsUUID,
14 Model, Scopes,
15 Table,
16 UpdatedAt
17 } from 'sequelize-typescript'
18 import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
19 import { activityPubCollection } from '../../helpers'
20 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
21 import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
22 import { CONSTRAINTS_FIELDS } from '../../initializers'
23 import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
24 import { sendDeleteVideoChannel } from '../../lib/activitypub/send'
25 import { AccountModel } from '../account/account'
26 import { ServerModel } from '../server/server'
27 import { getSort, throwIfNotValid } from '../utils'
28 import { VideoModel } from './video'
29 import { VideoChannelShareModel } from './video-channel-share'
30
31 enum ScopeNames {
32 WITH_ACCOUNT = 'WITH_ACCOUNT',
33 WITH_VIDEOS = 'WITH_VIDEOS'
34 }
35
36 @Scopes({
37 [ScopeNames.WITH_ACCOUNT]: {
38 include: [
39 {
40 model: () => AccountModel,
41 include: [ { model: () => ServerModel, required: false } ]
42 }
43 ]
44 },
45 [ScopeNames.WITH_VIDEOS]: {
46 include: [
47 () => VideoModel
48 ]
49 }
50 })
51 @Table({
52 tableName: 'videoChannel',
53 indexes: [
54 {
55 fields: [ 'accountId' ]
56 }
57 ]
58 })
59 export class VideoChannelModel extends Model<VideoChannelModel> {
60
61 @AllowNull(false)
62 @Default(DataType.UUIDV4)
63 @IsUUID(4)
64 @Column(DataType.UUID)
65 uuid: string
66
67 @AllowNull(false)
68 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
69 @Column
70 name: string
71
72 @AllowNull(true)
73 @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description'))
74 @Column
75 description: string
76
77 @AllowNull(false)
78 @Column
79 remote: boolean
80
81 @AllowNull(false)
82 @Is('VideoChannelUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
83 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max))
84 url: string
85
86 @CreatedAt
87 createdAt: Date
88
89 @UpdatedAt
90 updatedAt: Date
91
92 @ForeignKey(() => AccountModel)
93 @Column
94 accountId: number
95
96 @BelongsTo(() => AccountModel, {
97 foreignKey: {
98 allowNull: false
99 },
100 onDelete: 'CASCADE'
101 })
102 Account: AccountModel
103
104 @HasMany(() => VideoModel, {
105 foreignKey: {
106 name: 'channelId',
107 allowNull: false
108 },
109 onDelete: 'CASCADE'
110 })
111 Videos: VideoModel[]
112
113 @HasMany(() => VideoChannelShareModel, {
114 foreignKey: {
115 name: 'channelId',
116 allowNull: false
117 },
118 onDelete: 'CASCADE'
119 })
120 VideoChannelShares: VideoChannelShareModel[]
121
122 @AfterDestroy
123 static sendDeleteIfOwned (instance: VideoChannelModel) {
124 if (instance.isOwned()) {
125 return sendDeleteVideoChannel(instance, undefined)
126 }
127
128 return undefined
129 }
130
131 static countByAccount (accountId: number) {
132 const query = {
133 where: {
134 accountId
135 }
136 }
137
138 return VideoChannelModel.count(query)
139 }
140
141 static listForApi (start: number, count: number, sort: string) {
142 const query = {
143 offset: start,
144 limit: count,
145 order: [ getSort(sort) ]
146 }
147
148 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query)
149 .then(({ rows, count }) => {
150 return { total: count, data: rows }
151 })
152 }
153
154 static listByAccount (accountId: number) {
155 const query = {
156 order: [ getSort('createdAt') ],
157 include: [
158 {
159 model: AccountModel,
160 where: {
161 id: accountId
162 },
163 required: true,
164 include: [ { model: ServerModel, required: false } ]
165 }
166 ]
167 }
168
169 return VideoChannelModel.findAndCountAll(query)
170 .then(({ rows, count }) => {
171 return { total: count, data: rows }
172 })
173 }
174
175 static loadByUrl (url: string, t?: Sequelize.Transaction) {
176 const query: IFindOptions<VideoChannelModel> = {
177 where: {
178 url
179 }
180 }
181
182 if (t !== undefined) query.transaction = t
183
184 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
185 }
186
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 }
196
197 if (t !== undefined) query.transaction = t
198
199 return VideoChannelModel.findOne(query)
200 }
201
202 static loadByIdAndAccount (id: number, accountId: number) {
203 const options = {
204 where: {
205 id,
206 accountId
207 }
208 }
209
210 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
211 }
212
213 static loadAndPopulateAccount (id: number) {
214 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id)
215 }
216
217 static loadByUUIDAndPopulateAccount (uuid: string) {
218 const options = {
219 where: {
220 uuid
221 }
222 }
223
224 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
225 }
226
227 static loadAndPopulateAccountAndVideos (id: number) {
228 const options = {
229 include: [
230 VideoModel
231 ]
232 }
233
234 return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options)
235 }
236
237 isOwned () {
238 return this.remote === false
239 }
240
241 toFormattedJSON () {
242 const json = {
243 id: this.id,
244 uuid: this.uuid,
245 name: this.name,
246 description: this.description,
247 isLocal: this.isOwned(),
248 createdAt: this.createdAt,
249 updatedAt: this.updatedAt
250 }
251
252 if (this.Account !== undefined) {
253 json[ 'owner' ] = {
254 name: this.Account.name,
255 uuid: this.Account.uuid
256 }
257 }
258
259 if (Array.isArray(this.Videos)) {
260 json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON())
261 }
262
263 return json
264 }
265
266 toActivityPubObject () {
267 let sharesObject
268 if (Array.isArray(this.VideoChannelShares)) {
269 const shares: string[] = []
270
271 for (const videoChannelShare of this.VideoChannelShares) {
272 const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account)
273 shares.push(shareUrl)
274 }
275
276 sharesObject = activityPubCollection(shares)
277 }
278
279 return {
280 type: 'VideoChannel' as 'VideoChannel',
281 id: this.url,
282 uuid: this.uuid,
283 content: this.description,
284 name: this.name,
285 published: this.createdAt.toISOString(),
286 updated: this.updatedAt.toISOString(),
287 shares: sharesObject
288 }
289 }
290 }