Save
[oweals/peertube.git] / server / models / activitypub / actor.ts
1 import { join } from 'path'
2 import * as Sequelize from 'sequelize'
3 import {
4   AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table,
5   UpdatedAt
6 } from 'sequelize-typescript'
7 import { Avatar } from '../../../shared/models/avatars/avatar.model'
8 import { activityPubContextify } from '../../helpers'
9 import {
10   isActivityPubUrlValid,
11   isActorFollowersCountValid,
12   isActorFollowingCountValid, isActorPreferredUsernameValid,
13   isActorPrivateKeyValid,
14   isActorPublicKeyValid
15 } from '../../helpers/custom-validators/activitypub'
16 import { isUserUsernameValid } from '../../helpers/custom-validators/users'
17 import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
18 import { AccountFollowModel } from '../account/account-follow'
19 import { AvatarModel } from '../avatar/avatar'
20 import { ServerModel } from '../server/server'
21 import { throwIfNotValid } from '../utils'
22
23 @Table({
24   tableName: 'actor'
25 })
26 export class ActorModel extends Model<ActorModel> {
27
28   @AllowNull(false)
29   @Default(DataType.UUIDV4)
30   @IsUUID(4)
31   @Column(DataType.UUID)
32   uuid: string
33
34   @AllowNull(false)
35   @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name'))
36   @Column
37   name: string
38
39   @AllowNull(false)
40   @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
41   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
42   url: string
43
44   @AllowNull(true)
45   @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key'))
46   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY.max))
47   publicKey: string
48
49   @AllowNull(true)
50   @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key'))
51   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY.max))
52   privateKey: string
53
54   @AllowNull(false)
55   @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowersCountValid, 'followers count'))
56   @Column
57   followersCount: number
58
59   @AllowNull(false)
60   @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowingCountValid, 'following count'))
61   @Column
62   followingCount: number
63
64   @AllowNull(false)
65   @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
66   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
67   inboxUrl: string
68
69   @AllowNull(false)
70   @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
71   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
72   outboxUrl: string
73
74   @AllowNull(false)
75   @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
76   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
77   sharedInboxUrl: string
78
79   @AllowNull(false)
80   @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
81   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
82   followersUrl: string
83
84   @AllowNull(false)
85   @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
86   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
87   followingUrl: string
88
89   @CreatedAt
90   createdAt: Date
91
92   @UpdatedAt
93   updatedAt: Date
94
95   @ForeignKey(() => AvatarModel)
96   @Column
97   avatarId: number
98
99   @BelongsTo(() => AvatarModel, {
100     foreignKey: {
101       allowNull: true
102     },
103     onDelete: 'cascade'
104   })
105   Avatar: AvatarModel
106
107   @HasMany(() => AccountFollowModel, {
108     foreignKey: {
109       name: 'accountId',
110       allowNull: false
111     },
112     onDelete: 'cascade'
113   })
114   AccountFollowing: AccountFollowModel[]
115
116   @HasMany(() => AccountFollowModel, {
117     foreignKey: {
118       name: 'targetAccountId',
119       allowNull: false
120     },
121     as: 'followers',
122     onDelete: 'cascade'
123   })
124   AccountFollowers: AccountFollowModel[]
125
126   @ForeignKey(() => ServerModel)
127   @Column
128   serverId: number
129
130   @BelongsTo(() => ServerModel, {
131     foreignKey: {
132       allowNull: true
133     },
134     onDelete: 'cascade'
135   })
136   Server: ServerModel
137
138   static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
139     const query = {
140       where: {
141         followersUrl: {
142           [ Sequelize.Op.in ]: followersUrls
143         }
144       },
145       transaction
146     }
147
148     return ActorModel.findAll(query)
149   }
150
151   toFormattedJSON () {
152     let avatar: Avatar = null
153     if (this.Avatar) {
154       avatar = {
155         path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
156         createdAt: this.Avatar.createdAt,
157         updatedAt: this.Avatar.updatedAt
158       }
159     }
160
161     let host = CONFIG.WEBSERVER.HOST
162     let score: number
163     if (this.Server) {
164       host = this.Server.host
165       score = this.Server.score
166     }
167
168     return {
169       id: this.id,
170       host,
171       score,
172       followingCount: this.followingCount,
173       followersCount: this.followersCount,
174       avatar
175     }
176   }
177
178   toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') {
179     let activityPubType
180     if (type === 'Account') {
181       activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
182     } else { // VideoChannel
183       activityPubType = 'Group'
184     }
185
186     const json = {
187       type,
188       id: this.url,
189       following: this.getFollowingUrl(),
190       followers: this.getFollowersUrl(),
191       inbox: this.inboxUrl,
192       outbox: this.outboxUrl,
193       preferredUsername: name,
194       url: this.url,
195       name,
196       endpoints: {
197         sharedInbox: this.sharedInboxUrl
198       },
199       uuid,
200       publicKey: {
201         id: this.getPublicKeyUrl(),
202         owner: this.url,
203         publicKeyPem: this.publicKey
204       }
205     }
206
207     return activityPubContextify(json)
208   }
209
210   getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
211     const query = {
212       attributes: [ 'sharedInboxUrl' ],
213       include: [
214         {
215           model: AccountFollowModel,
216           required: true,
217           as: 'followers',
218           where: {
219             targetAccountId: this.id
220           }
221         }
222       ],
223       transaction: t
224     }
225
226     return ActorModel.findAll(query)
227       .then(accounts => accounts.map(a => a.sharedInboxUrl))
228   }
229
230   getFollowingUrl () {
231     return this.url + '/following'
232   }
233
234   getFollowersUrl () {
235     return this.url + '/followers'
236   }
237
238   getPublicKeyUrl () {
239     return this.url + '#main-key'
240   }
241
242   isOwned () {
243     return this.serverId === null
244   }
245 }