]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/account/account.ts
9a29215018ef5122e8040d42a9271651564ffc88
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
1 import * as Sequelize from 'sequelize'
2 import {
3 activityPubContextify,
4 isAccountFollowersCountValid,
5 isAccountFollowersValid,
6 isAccountFollowingCountValid,
7 isAccountFollowingValid,
8 isAccountInboxValid,
9 isAccountOutboxValid,
10 isAccountPrivateKeyValid,
11 isAccountPublicKeyValid,
12 isAccountSharedInboxValid,
13 isAccountUrlValid,
14 isUserUsernameValid
15 } from '../../helpers'
16 import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
17 import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
18
19 import { addMethodsToModel } from '../utils'
20 import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
21
22 let Account: Sequelize.Model<AccountInstance, AccountAttributes>
23 let loadAccountByServerAndUUID: AccountMethods.LoadAccountByServerAndUUID
24 let load: AccountMethods.Load
25 let loadApplication: AccountMethods.LoadApplication
26 let loadByUUID: AccountMethods.LoadByUUID
27 let loadByUrl: AccountMethods.LoadByUrl
28 let loadLocalByName: AccountMethods.LoadLocalByName
29 let loadByNameAndHost: AccountMethods.LoadByNameAndHost
30 let listOwned: AccountMethods.ListOwned
31 let isOwned: AccountMethods.IsOwned
32 let toActivityPubObject: AccountMethods.ToActivityPubObject
33 let toFormattedJSON: AccountMethods.ToFormattedJSON
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: {
54 nameValid: value => {
55 const res = isUserUsernameValid(value)
56 if (res === false) throw new Error('Name is not valid.')
57 }
58 }
59 },
60 url: {
61 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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: {
71 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
72 allowNull: true,
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: {
81 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
82 allowNull: true,
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: {
104 followingCountValid: value => {
105 const res = isAccountFollowingCountValid(value)
106 if (res === false) throw new Error('Following count is not valid.')
107 }
108 }
109 },
110 inboxUrl: {
111 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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: {
121 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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: {
131 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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: {
141 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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: {
151 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
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 {
167 fields: [ 'serverId' ]
168 },
169 {
170 fields: [ 'userId' ],
171 unique: true
172 },
173 {
174 fields: [ 'applicationId' ],
175 unique: true
176 },
177 {
178 fields: [ 'name', 'serverId', 'applicationId' ],
179 unique: true
180 }
181 ],
182 hooks: { afterDestroy }
183 }
184 )
185
186 const classMethods = [
187 associate,
188 loadAccountByServerAndUUID,
189 loadApplication,
190 load,
191 loadByUUID,
192 loadByUrl,
193 loadLocalByName,
194 loadByNameAndHost,
195 listOwned
196 ]
197 const instanceMethods = [
198 isOwned,
199 toActivityPubObject,
200 toFormattedJSON,
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) {
214 Account.belongsTo(models.Server, {
215 foreignKey: {
216 name: 'serverId',
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: {
232 name: 'applicationId',
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
247 Account.hasMany(models.AccountFollow, {
248 foreignKey: {
249 name: 'accountId',
250 allowNull: false
251 },
252 onDelete: 'cascade'
253 })
254
255 Account.hasMany(models.AccountFollow, {
256 foreignKey: {
257 name: 'targetAccountId',
258 allowNull: false
259 },
260 as: 'followers',
261 onDelete: 'cascade'
262 })
263 }
264
265 function afterDestroy (account: AccountInstance) {
266 if (account.isOwned()) {
267 return sendDeleteAccount(account, undefined)
268 }
269
270 return undefined
271 }
272
273 toFormattedJSON = function (this: AccountInstance) {
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 }
281
282 const json = {
283 id: this.id,
284 host,
285 score,
286 name: this.name,
287 createdAt: this.createdAt,
288 updatedAt: this.updatedAt
289 }
290
291 return json
292 }
293
294 toActivityPubObject = function (this: AccountInstance) {
295 const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
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) {
322 return this.serverId === null
323 }
324
325 getFollowerSharedInboxUrls = function (this: AccountInstance) {
326 const query: Sequelize.FindOptions<AccountAttributes> = {
327 attributes: [ 'sharedInboxUrl' ],
328 include: [
329 {
330 model: Account['sequelize'].models.AccountFollow,
331 required: true,
332 as: 'followers',
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) {
345 return this.url + '/following'
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: {
361 serverId: null
362 }
363 }
364
365 return Account.findAll(query)
366 }
367
368 loadApplication = function () {
369 return Account.findOne({
370 include: [
371 {
372 model: Account['sequelize'].models.Application,
373 required: true
374 }
375 ]
376 })
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
393 loadLocalByName = function (name: string) {
394 const query: Sequelize.FindOptions<AccountAttributes> = {
395 where: {
396 name,
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
419 },
420 include: [
421 {
422 model: Account['sequelize'].models.Server,
423 required: true,
424 where: {
425 host
426 }
427 }
428 ]
429 }
430
431 return Account.findOne(query)
432 }
433
434 loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
435 const query: Sequelize.FindOptions<AccountAttributes> = {
436 where: {
437 url
438 },
439 transaction
440 }
441
442 return Account.findOne(query)
443 }
444
445 loadAccountByServerAndUUID = function (uuid: string, serverId: number, transaction: Sequelize.Transaction) {
446 const query: Sequelize.FindOptions<AccountAttributes> = {
447 where: {
448 serverId,
449 uuid
450 },
451 transaction
452 }
453
454 return Account.find(query)
455 }