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