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