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