]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/account.ts
Refractor activity pub lib/helpers
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
CommitLineData
e4f97bab 1import * as Sequelize from 'sequelize'
e4f97bab 2import {
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 16import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
54141398 17import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
79d5caf9
C
18
19import { addMethodsToModel } from '../utils'
20import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
e4f97bab
C
21
22let Account: Sequelize.Model<AccountInstance, AccountAttributes>
60862425 23let loadAccountByServerAndUUID: AccountMethods.LoadAccountByServerAndUUID
e4f97bab 24let load: AccountMethods.Load
7a7724e6 25let loadApplication: AccountMethods.LoadApplication
e4f97bab
C
26let loadByUUID: AccountMethods.LoadByUUID
27let loadByUrl: AccountMethods.LoadByUrl
350e31d6
C
28let loadLocalByName: AccountMethods.LoadLocalByName
29let loadByNameAndHost: AccountMethods.LoadByNameAndHost
e4f97bab 30let listOwned: AccountMethods.ListOwned
e4f97bab
C
31let isOwned: AccountMethods.IsOwned
32let toActivityPubObject: AccountMethods.ToActivityPubObject
7a7724e6 33let toFormattedJSON: AccountMethods.ToFormattedJSON
e4f97bab
C
34let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
35let getFollowingUrl: AccountMethods.GetFollowingUrl
36let getFollowersUrl: AccountMethods.GetFollowersUrl
37let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl
38
39export 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
213function 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
265function afterDestroy (account: AccountInstance) {
266 if (account.isOwned()) {
7a7724e6 267 return sendDeleteAccount(account, undefined)
e4f97bab
C
268 }
269
270 return undefined
271}
272
7a7724e6 273toFormattedJSON = 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 294toActivityPubObject = 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
321isOwned = function (this: AccountInstance) {
60862425 322 return this.serverId === null
e4f97bab
C
323}
324
325getFollowerSharedInboxUrls = 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
344getFollowingUrl = function (this: AccountInstance) {
51548b31 345 return this.url + '/following'
e4f97bab
C
346}
347
348getFollowersUrl = function (this: AccountInstance) {
349 return this.url + '/followers'
350}
351
352getPublicKeyUrl = function (this: AccountInstance) {
353 return this.url + '#main-key'
354}
355
356// ------------------------------ STATICS ------------------------------
357
358listOwned = 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
368loadApplication = 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
379load = function (id: number) {
380 return Account.findById(id)
381}
382
383loadByUUID = function (uuid: string) {
384 const query: Sequelize.FindOptions<AccountAttributes> = {
385 where: {
386 uuid
387 }
388 }
389
390 return Account.findOne(query)
391}
392
350e31d6 393loadLocalByName = 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
415loadByNameAndHost = 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 434loadByUrl = 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 445loadAccountByServerAndUUID = 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}