]>
Commit | Line | Data |
---|---|---|
e4f97bab C |
1 | import * as Sequelize from 'sequelize' |
2 | ||
3 | import { | |
79d5caf9 | 4 | activityPubContextify, |
e4f97bab | 5 | isAccountFollowersCountValid, |
79d5caf9 | 6 | isAccountFollowersValid, |
e4f97bab | 7 | isAccountFollowingCountValid, |
79d5caf9 | 8 | isAccountFollowingValid, |
e4f97bab C |
9 | isAccountInboxValid, |
10 | isAccountOutboxValid, | |
79d5caf9 C |
11 | isAccountPrivateKeyValid, |
12 | isAccountPublicKeyValid, | |
e4f97bab | 13 | isAccountSharedInboxValid, |
79d5caf9 C |
14 | isAccountUrlValid, |
15 | isUserUsernameValid | |
e4f97bab | 16 | } from '../../helpers' |
51548b31 | 17 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' |
79d5caf9 C |
18 | import { sendDeleteAccount } from '../../lib/activitypub/send-request' |
19 | ||
20 | import { addMethodsToModel } from '../utils' | |
21 | import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' | |
e4f97bab C |
22 | |
23 | let Account: Sequelize.Model<AccountInstance, AccountAttributes> | |
60862425 | 24 | let loadAccountByServerAndUUID: AccountMethods.LoadAccountByServerAndUUID |
e4f97bab | 25 | let load: AccountMethods.Load |
7a7724e6 | 26 | let loadApplication: AccountMethods.LoadApplication |
e4f97bab C |
27 | let loadByUUID: AccountMethods.LoadByUUID |
28 | let loadByUrl: AccountMethods.LoadByUrl | |
350e31d6 C |
29 | let loadLocalByName: AccountMethods.LoadLocalByName |
30 | let loadByNameAndHost: AccountMethods.LoadByNameAndHost | |
e4f97bab | 31 | let listOwned: AccountMethods.ListOwned |
e4f97bab C |
32 | let isOwned: AccountMethods.IsOwned |
33 | let toActivityPubObject: AccountMethods.ToActivityPubObject | |
7a7724e6 | 34 | let toFormattedJSON: AccountMethods.ToFormattedJSON |
e4f97bab C |
35 | let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls |
36 | let getFollowingUrl: AccountMethods.GetFollowingUrl | |
37 | let getFollowersUrl: AccountMethods.GetFollowersUrl | |
38 | let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl | |
39 | ||
40 | export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | |
41 | Account = sequelize.define<AccountInstance, AccountAttributes>('Account', | |
42 | { | |
43 | uuid: { | |
44 | type: DataTypes.UUID, | |
45 | defaultValue: DataTypes.UUIDV4, | |
46 | allowNull: false, | |
47 | validate: { | |
48 | isUUID: 4 | |
49 | } | |
50 | }, | |
51 | name: { | |
52 | type: DataTypes.STRING, | |
53 | allowNull: false, | |
54 | validate: { | |
e34c85e5 | 55 | nameValid: value => { |
e4f97bab | 56 | const res = isUserUsernameValid(value) |
e34c85e5 | 57 | if (res === false) throw new Error('Name is not valid.') |
e4f97bab C |
58 | } |
59 | } | |
60 | }, | |
61 | url: { | |
e34c85e5 | 62 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
63 | allowNull: false, |
64 | validate: { | |
65 | urlValid: value => { | |
66 | const res = isAccountUrlValid(value) | |
67 | if (res === false) throw new Error('URL is not valid.') | |
68 | } | |
69 | } | |
70 | }, | |
71 | publicKey: { | |
e34c85e5 | 72 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max), |
47e0652b | 73 | allowNull: true, |
e4f97bab C |
74 | validate: { |
75 | publicKeyValid: value => { | |
76 | const res = isAccountPublicKeyValid(value) | |
77 | if (res === false) throw new Error('Public key is not valid.') | |
78 | } | |
79 | } | |
80 | }, | |
81 | privateKey: { | |
e34c85e5 | 82 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max), |
350e31d6 | 83 | allowNull: true, |
e4f97bab C |
84 | validate: { |
85 | privateKeyValid: value => { | |
86 | const res = isAccountPrivateKeyValid(value) | |
87 | if (res === false) throw new Error('Private key is not valid.') | |
88 | } | |
89 | } | |
90 | }, | |
91 | followersCount: { | |
92 | type: DataTypes.INTEGER, | |
93 | allowNull: false, | |
94 | validate: { | |
95 | followersCountValid: value => { | |
96 | const res = isAccountFollowersCountValid(value) | |
97 | if (res === false) throw new Error('Followers count is not valid.') | |
98 | } | |
99 | } | |
100 | }, | |
101 | followingCount: { | |
102 | type: DataTypes.INTEGER, | |
103 | allowNull: false, | |
104 | validate: { | |
e34c85e5 | 105 | followingCountValid: value => { |
e4f97bab C |
106 | const res = isAccountFollowingCountValid(value) |
107 | if (res === false) throw new Error('Following count is not valid.') | |
108 | } | |
109 | } | |
110 | }, | |
111 | inboxUrl: { | |
e34c85e5 | 112 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
113 | allowNull: false, |
114 | validate: { | |
115 | inboxUrlValid: value => { | |
116 | const res = isAccountInboxValid(value) | |
117 | if (res === false) throw new Error('Inbox URL is not valid.') | |
118 | } | |
119 | } | |
120 | }, | |
121 | outboxUrl: { | |
e34c85e5 | 122 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
123 | allowNull: false, |
124 | validate: { | |
125 | outboxUrlValid: value => { | |
126 | const res = isAccountOutboxValid(value) | |
127 | if (res === false) throw new Error('Outbox URL is not valid.') | |
128 | } | |
129 | } | |
130 | }, | |
131 | sharedInboxUrl: { | |
e34c85e5 | 132 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
133 | allowNull: false, |
134 | validate: { | |
135 | sharedInboxUrlValid: value => { | |
136 | const res = isAccountSharedInboxValid(value) | |
137 | if (res === false) throw new Error('Shared inbox URL is not valid.') | |
138 | } | |
139 | } | |
140 | }, | |
141 | followersUrl: { | |
e34c85e5 | 142 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
143 | allowNull: false, |
144 | validate: { | |
145 | followersUrlValid: value => { | |
146 | const res = isAccountFollowersValid(value) | |
147 | if (res === false) throw new Error('Followers URL is not valid.') | |
148 | } | |
149 | } | |
150 | }, | |
151 | followingUrl: { | |
e34c85e5 | 152 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), |
e4f97bab C |
153 | allowNull: false, |
154 | validate: { | |
155 | followingUrlValid: value => { | |
156 | const res = isAccountFollowingValid(value) | |
157 | if (res === false) throw new Error('Following URL is not valid.') | |
158 | } | |
159 | } | |
160 | } | |
161 | }, | |
162 | { | |
163 | indexes: [ | |
164 | { | |
165 | fields: [ 'name' ] | |
166 | }, | |
167 | { | |
60862425 | 168 | fields: [ 'serverId' ] |
e4f97bab C |
169 | }, |
170 | { | |
171 | fields: [ 'userId' ], | |
172 | unique: true | |
173 | }, | |
174 | { | |
175 | fields: [ 'applicationId' ], | |
176 | unique: true | |
177 | }, | |
178 | { | |
60862425 | 179 | fields: [ 'name', 'serverId', 'applicationId' ], |
e4f97bab C |
180 | unique: true |
181 | } | |
182 | ], | |
183 | hooks: { afterDestroy } | |
184 | } | |
185 | ) | |
186 | ||
187 | const classMethods = [ | |
188 | associate, | |
60862425 | 189 | loadAccountByServerAndUUID, |
7a7724e6 | 190 | loadApplication, |
e4f97bab C |
191 | load, |
192 | loadByUUID, | |
ce548a10 | 193 | loadByUrl, |
350e31d6 C |
194 | loadLocalByName, |
195 | loadByNameAndHost, | |
51548b31 | 196 | listOwned |
e4f97bab C |
197 | ] |
198 | const instanceMethods = [ | |
199 | isOwned, | |
200 | toActivityPubObject, | |
7a7724e6 | 201 | toFormattedJSON, |
e4f97bab C |
202 | getFollowerSharedInboxUrls, |
203 | getFollowingUrl, | |
204 | getFollowersUrl, | |
205 | getPublicKeyUrl | |
206 | ] | |
207 | addMethodsToModel(Account, classMethods, instanceMethods) | |
208 | ||
209 | return Account | |
210 | } | |
211 | ||
212 | // --------------------------------------------------------------------------- | |
213 | ||
214 | function associate (models) { | |
60862425 | 215 | Account.belongsTo(models.Server, { |
e4f97bab | 216 | foreignKey: { |
60862425 | 217 | name: 'serverId', |
e4f97bab C |
218 | allowNull: true |
219 | }, | |
220 | onDelete: 'cascade' | |
221 | }) | |
222 | ||
223 | Account.belongsTo(models.User, { | |
224 | foreignKey: { | |
225 | name: 'userId', | |
226 | allowNull: true | |
227 | }, | |
228 | onDelete: 'cascade' | |
229 | }) | |
230 | ||
231 | Account.belongsTo(models.Application, { | |
232 | foreignKey: { | |
e34c85e5 | 233 | name: 'applicationId', |
e4f97bab C |
234 | allowNull: true |
235 | }, | |
236 | onDelete: 'cascade' | |
237 | }) | |
238 | ||
239 | Account.hasMany(models.VideoChannel, { | |
240 | foreignKey: { | |
241 | name: 'accountId', | |
242 | allowNull: false | |
243 | }, | |
244 | onDelete: 'cascade', | |
245 | hooks: true | |
246 | }) | |
247 | ||
e34c85e5 | 248 | Account.hasMany(models.AccountFollow, { |
e4f97bab C |
249 | foreignKey: { |
250 | name: 'accountId', | |
251 | allowNull: false | |
252 | }, | |
253 | onDelete: 'cascade' | |
254 | }) | |
255 | ||
e34c85e5 | 256 | Account.hasMany(models.AccountFollow, { |
e4f97bab C |
257 | foreignKey: { |
258 | name: 'targetAccountId', | |
259 | allowNull: false | |
260 | }, | |
8e10cf1a | 261 | as: 'followers', |
e4f97bab C |
262 | onDelete: 'cascade' |
263 | }) | |
264 | } | |
265 | ||
266 | function afterDestroy (account: AccountInstance) { | |
267 | if (account.isOwned()) { | |
7a7724e6 | 268 | return sendDeleteAccount(account, undefined) |
e4f97bab C |
269 | } |
270 | ||
271 | return undefined | |
272 | } | |
273 | ||
7a7724e6 | 274 | toFormattedJSON = function (this: AccountInstance) { |
60862425 C |
275 | let host = CONFIG.WEBSERVER.HOST |
276 | let score: number | |
277 | ||
278 | if (this.Server) { | |
279 | host = this.Server.host | |
280 | score = this.Server.score as number | |
281 | } | |
51548b31 | 282 | |
7a7724e6 C |
283 | const json = { |
284 | id: this.id, | |
51548b31 | 285 | host, |
60862425 C |
286 | score, |
287 | name: this.name, | |
288 | createdAt: this.createdAt, | |
289 | updatedAt: this.updatedAt | |
7a7724e6 C |
290 | } |
291 | ||
292 | return json | |
293 | } | |
294 | ||
e4f97bab | 295 | toActivityPubObject = function (this: AccountInstance) { |
60862425 | 296 | const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' |
e4f97bab C |
297 | |
298 | const json = { | |
299 | type, | |
300 | id: this.url, | |
301 | following: this.getFollowingUrl(), | |
302 | followers: this.getFollowersUrl(), | |
303 | inbox: this.inboxUrl, | |
304 | outbox: this.outboxUrl, | |
305 | preferredUsername: this.name, | |
306 | url: this.url, | |
307 | name: this.name, | |
308 | endpoints: { | |
309 | sharedInbox: this.sharedInboxUrl | |
310 | }, | |
311 | uuid: this.uuid, | |
312 | publicKey: { | |
313 | id: this.getPublicKeyUrl(), | |
314 | owner: this.url, | |
315 | publicKeyPem: this.publicKey | |
316 | } | |
317 | } | |
318 | ||
319 | return activityPubContextify(json) | |
320 | } | |
321 | ||
322 | isOwned = function (this: AccountInstance) { | |
60862425 | 323 | return this.serverId === null |
e4f97bab C |
324 | } |
325 | ||
326 | getFollowerSharedInboxUrls = function (this: AccountInstance) { | |
327 | const query: Sequelize.FindOptions<AccountAttributes> = { | |
328 | attributes: [ 'sharedInboxUrl' ], | |
329 | include: [ | |
330 | { | |
e34c85e5 | 331 | model: Account['sequelize'].models.AccountFollow, |
350e31d6 C |
332 | required: true, |
333 | as: 'followers', | |
e4f97bab C |
334 | where: { |
335 | targetAccountId: this.id | |
336 | } | |
337 | } | |
338 | ] | |
339 | } | |
340 | ||
341 | return Account.findAll(query) | |
342 | .then(accounts => accounts.map(a => a.sharedInboxUrl)) | |
343 | } | |
344 | ||
345 | getFollowingUrl = function (this: AccountInstance) { | |
51548b31 | 346 | return this.url + '/following' |
e4f97bab C |
347 | } |
348 | ||
349 | getFollowersUrl = function (this: AccountInstance) { | |
350 | return this.url + '/followers' | |
351 | } | |
352 | ||
353 | getPublicKeyUrl = function (this: AccountInstance) { | |
354 | return this.url + '#main-key' | |
355 | } | |
356 | ||
357 | // ------------------------------ STATICS ------------------------------ | |
358 | ||
359 | listOwned = function () { | |
360 | const query: Sequelize.FindOptions<AccountAttributes> = { | |
361 | where: { | |
60862425 | 362 | serverId: null |
e4f97bab C |
363 | } |
364 | } | |
365 | ||
366 | return Account.findAll(query) | |
367 | } | |
368 | ||
7a7724e6 C |
369 | loadApplication = function () { |
370 | return Account.findOne({ | |
371 | include: [ | |
372 | { | |
350e31d6 | 373 | model: Account['sequelize'].models.Application, |
7a7724e6 C |
374 | required: true |
375 | } | |
376 | ] | |
377 | }) | |
e4f97bab C |
378 | } |
379 | ||
380 | load = function (id: number) { | |
381 | return Account.findById(id) | |
382 | } | |
383 | ||
384 | loadByUUID = function (uuid: string) { | |
385 | const query: Sequelize.FindOptions<AccountAttributes> = { | |
386 | where: { | |
387 | uuid | |
388 | } | |
389 | } | |
390 | ||
391 | return Account.findOne(query) | |
392 | } | |
393 | ||
350e31d6 | 394 | loadLocalByName = function (name: string) { |
e4f97bab C |
395 | const query: Sequelize.FindOptions<AccountAttributes> = { |
396 | where: { | |
397 | name, | |
350e31d6 C |
398 | [Sequelize.Op.or]: [ |
399 | { | |
400 | userId: { | |
401 | [Sequelize.Op.ne]: null | |
402 | } | |
403 | }, | |
404 | { | |
405 | applicationId: { | |
406 | [Sequelize.Op.ne]: null | |
407 | } | |
408 | } | |
409 | ] | |
410 | } | |
411 | } | |
412 | ||
413 | return Account.findOne(query) | |
414 | } | |
415 | ||
416 | loadByNameAndHost = function (name: string, host: string) { | |
417 | const query: Sequelize.FindOptions<AccountAttributes> = { | |
418 | where: { | |
419 | name | |
7a7724e6 C |
420 | }, |
421 | include: [ | |
422 | { | |
60862425 | 423 | model: Account['sequelize'].models.Server, |
350e31d6 | 424 | required: true, |
7a7724e6 C |
425 | where: { |
426 | host | |
427 | } | |
428 | } | |
429 | ] | |
e4f97bab C |
430 | } |
431 | ||
432 | return Account.findOne(query) | |
433 | } | |
434 | ||
ce548a10 | 435 | loadByUrl = function (url: string, transaction?: Sequelize.Transaction) { |
e4f97bab C |
436 | const query: Sequelize.FindOptions<AccountAttributes> = { |
437 | where: { | |
438 | url | |
ce548a10 C |
439 | }, |
440 | transaction | |
e4f97bab C |
441 | } |
442 | ||
443 | return Account.findOne(query) | |
444 | } | |
445 | ||
60862425 | 446 | loadAccountByServerAndUUID = function (uuid: string, serverId: number, transaction: Sequelize.Transaction) { |
e4f97bab C |
447 | const query: Sequelize.FindOptions<AccountAttributes> = { |
448 | where: { | |
60862425 | 449 | serverId, |
e4f97bab C |
450 | uuid |
451 | }, | |
452 | transaction | |
453 | } | |
454 | ||
455 | return Account.find(query) | |
456 | } |