diff options
Diffstat (limited to 'server/models/activitypub')
-rw-r--r-- | server/models/activitypub/actor.ts | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts new file mode 100644 index 000000000..4cae6a6ec --- /dev/null +++ b/server/models/activitypub/actor.ts | |||
@@ -0,0 +1,245 @@ | |||
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 | } | ||