Enh #106 : Add an autoPlayVideo user attribute (#159)
[oweals/peertube.git] / server / models / account / account.ts
1 import { join } from 'path'
2 import * as Sequelize from 'sequelize'
3 import {
4   AfterDestroy,
5   AllowNull,
6   BelongsTo,
7   Column,
8   CreatedAt,
9   DataType,
10   Default,
11   ForeignKey,
12   HasMany,
13   Is,
14   IsUUID,
15   Model,
16   Table,
17   UpdatedAt
18 } from 'sequelize-typescript'
19 import { Avatar } from '../../../shared/models/avatars/avatar.model'
20 import { activityPubContextify } from '../../helpers'
21 import {
22   isAccountFollowersCountValid,
23   isAccountFollowingCountValid,
24   isAccountPrivateKeyValid,
25   isAccountPublicKeyValid,
26   isActivityPubUrlValid
27 } from '../../helpers/custom-validators/activitypub'
28 import { isUserUsernameValid } from '../../helpers/custom-validators/users'
29 import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
30 import { sendDeleteAccount } from '../../lib/activitypub/send'
31 import { ApplicationModel } from '../application/application'
32 import { AvatarModel } from '../avatar/avatar'
33 import { ServerModel } from '../server/server'
34 import { throwIfNotValid } from '../utils'
35 import { VideoChannelModel } from '../video/video-channel'
36 import { AccountFollowModel } from './account-follow'
37 import { UserModel } from './user'
38
39 @Table({
40   tableName: 'account',
41   indexes: [
42     {
43       fields: [ 'name' ]
44     },
45     {
46       fields: [ 'serverId' ]
47     },
48     {
49       fields: [ 'userId' ],
50       unique: true
51     },
52     {
53       fields: [ 'applicationId' ],
54       unique: true
55     },
56     {
57       fields: [ 'name', 'serverId', 'applicationId' ],
58       unique: true
59     }
60   ]
61 })
62 export class AccountModel extends Model<Account> {
63
64   @AllowNull(false)
65   @Default(DataType.UUIDV4)
66   @IsUUID(4)
67   @Column(DataType.UUID)
68   uuid: string
69
70   @AllowNull(false)
71   @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
72   @Column
73   name: string
74
75   @AllowNull(false)
76   @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
77   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
78   url: string
79
80   @AllowNull(true)
81   @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key'))
82   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max))
83   publicKey: string
84
85   @AllowNull(true)
86   @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key'))
87   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max))
88   privateKey: string
89
90   @AllowNull(false)
91   @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count'))
92   @Column
93   followersCount: number
94
95   @AllowNull(false)
96   @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count'))
97   @Column
98   followingCount: number
99
100   @AllowNull(false)
101   @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
102   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
103   inboxUrl: string
104
105   @AllowNull(false)
106   @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
107   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
108   outboxUrl: string
109
110   @AllowNull(false)
111   @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
112   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
113   sharedInboxUrl: string
114
115   @AllowNull(false)
116   @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
117   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
118   followersUrl: string
119
120   @AllowNull(false)
121   @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
122   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
123   followingUrl: string
124
125   @CreatedAt
126   createdAt: Date
127
128   @UpdatedAt
129   updatedAt: Date
130
131   @ForeignKey(() => AvatarModel)
132   @Column
133   avatarId: number
134
135   @BelongsTo(() => AvatarModel, {
136     foreignKey: {
137       allowNull: true
138     },
139     onDelete: 'cascade'
140   })
141   Avatar: AvatarModel
142
143   @ForeignKey(() => ServerModel)
144   @Column
145   serverId: number
146
147   @BelongsTo(() => ServerModel, {
148     foreignKey: {
149       allowNull: true
150     },
151     onDelete: 'cascade'
152   })
153   Server: ServerModel
154
155   @ForeignKey(() => UserModel)
156   @Column
157   userId: number
158
159   @BelongsTo(() => UserModel, {
160     foreignKey: {
161       allowNull: true
162     },
163     onDelete: 'cascade'
164   })
165   User: UserModel
166
167   @ForeignKey(() => ApplicationModel)
168   @Column
169   applicationId: number
170
171   @BelongsTo(() => ApplicationModel, {
172     foreignKey: {
173       allowNull: true
174     },
175     onDelete: 'cascade'
176   })
177   Application: ApplicationModel
178
179   @HasMany(() => VideoChannelModel, {
180     foreignKey: {
181       allowNull: false
182     },
183     onDelete: 'cascade',
184     hooks: true
185   })
186   VideoChannels: VideoChannelModel[]
187
188   @HasMany(() => AccountFollowModel, {
189     foreignKey: {
190       name: 'accountId',
191       allowNull: false
192     },
193     onDelete: 'cascade'
194   })
195   AccountFollowing: AccountFollowModel[]
196
197   @HasMany(() => AccountFollowModel, {
198     foreignKey: {
199       name: 'targetAccountId',
200       allowNull: false
201     },
202     as: 'followers',
203     onDelete: 'cascade'
204   })
205   AccountFollowers: AccountFollowModel[]
206
207   @AfterDestroy
208   static sendDeleteIfOwned (instance: AccountModel) {
209     if (instance.isOwned()) {
210       return sendDeleteAccount(instance, undefined)
211     }
212
213     return undefined
214   }
215
216   static loadApplication () {
217     return AccountModel.findOne({
218       include: [
219         {
220           model: ApplicationModel,
221           required: true
222         }
223       ]
224     })
225   }
226
227   static load (id: number) {
228     return AccountModel.findById(id)
229   }
230
231   static loadByUUID (uuid: string) {
232     const query = {
233       where: {
234         uuid
235       }
236     }
237
238     return AccountModel.findOne(query)
239   }
240
241   static loadLocalByName (name: string) {
242     const query = {
243       where: {
244         name,
245         [ Sequelize.Op.or ]: [
246           {
247             userId: {
248               [ Sequelize.Op.ne ]: null
249             }
250           },
251           {
252             applicationId: {
253               [ Sequelize.Op.ne ]: null
254             }
255           }
256         ]
257       }
258     }
259
260     return AccountModel.findOne(query)
261   }
262
263   static loadByNameAndHost (name: string, host: string) {
264     const query = {
265       where: {
266         name
267       },
268       include: [
269         {
270           model: ServerModel,
271           required: true,
272           where: {
273             host
274           }
275         }
276       ]
277     }
278
279     return AccountModel.findOne(query)
280   }
281
282   static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
283     const query = {
284       where: {
285         url
286       },
287       transaction
288     }
289
290     return AccountModel.findOne(query)
291   }
292
293   static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
294     const query = {
295       where: {
296         followersUrl: {
297           [ Sequelize.Op.in ]: followersUrls
298         }
299       },
300       transaction
301     }
302
303     return AccountModel.findAll(query)
304   }
305
306   toFormattedJSON () {
307     let host = CONFIG.WEBSERVER.HOST
308     let score: number
309     let avatar: Avatar = null
310
311     if (this.Avatar) {
312       avatar = {
313         path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
314         createdAt: this.Avatar.createdAt,
315         updatedAt: this.Avatar.updatedAt
316       }
317     }
318
319     if (this.Server) {
320       host = this.Server.host
321       score = this.Server.score
322     }
323
324     return {
325       id: this.id,
326       uuid: this.uuid,
327       host,
328       score,
329       name: this.name,
330       followingCount: this.followingCount,
331       followersCount: this.followersCount,
332       createdAt: this.createdAt,
333       updatedAt: this.updatedAt,
334       avatar
335     }
336   }
337
338   toActivityPubObject () {
339     const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
340
341     const json = {
342       type,
343       id: this.url,
344       following: this.getFollowingUrl(),
345       followers: this.getFollowersUrl(),
346       inbox: this.inboxUrl,
347       outbox: this.outboxUrl,
348       preferredUsername: this.name,
349       url: this.url,
350       name: this.name,
351       endpoints: {
352         sharedInbox: this.sharedInboxUrl
353       },
354       uuid: this.uuid,
355       publicKey: {
356         id: this.getPublicKeyUrl(),
357         owner: this.url,
358         publicKeyPem: this.publicKey
359       }
360     }
361
362     return activityPubContextify(json)
363   }
364
365   isOwned () {
366     return this.serverId === null
367   }
368
369   getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
370     const query = {
371       attributes: [ 'sharedInboxUrl' ],
372       include: [
373         {
374           model: AccountFollowModel,
375           required: true,
376           as: 'followers',
377           where: {
378             targetAccountId: this.id
379           }
380         }
381       ],
382       transaction: t
383     }
384
385     return AccountModel.findAll(query)
386       .then(accounts => accounts.map(a => a.sharedInboxUrl))
387   }
388
389   getFollowingUrl () {
390     return this.url + '/following'
391   }
392
393   getFollowersUrl () {
394     return this.url + '/followers'
395   }
396
397   getPublicKeyUrl () {
398     return this.url + '#main-key'
399   }
400 }