]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/account/account.ts
d6758fa109dd144e80747a47ea8a59038bde7f33
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
1 import { join } from 'path'
2 import * as Sequelize from 'sequelize'
3 import {
4 AfterDestroy,
5 AllowNull,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 DataType,
10 Default,
11 ForeignKey,
12 HasMany,
13 Is,
14 IsUUID,
15 Model,
16 Table,
17 UpdatedAt
18 } from 'sequelize-typescript'
19 import { Avatar } from '../../../shared/models/avatars/avatar.model'
20 import { activityPubContextify } from '../../helpers'
21 import {
22 isAccountFollowersCountValid,
23 isAccountFollowingCountValid,
24 isAccountPrivateKeyValid,
25 isAccountPublicKeyValid,
26 isActivityPubUrlValid
27 } from '../../helpers/custom-validators/activitypub'
28 import { isUserUsernameValid } from '../../helpers/custom-validators/users'
29 import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
30 import { sendDeleteAccount } from '../../lib/activitypub/send'
31 import { ApplicationModel } from '../application/application'
32 import { AvatarModel } from '../avatar/avatar'
33 import { ServerModel } from '../server/server'
34 import { throwIfNotValid } from '../utils'
35 import { VideoChannelModel } from '../video/video-channel'
36 import { AccountFollowModel } from './account-follow'
37 import { UserModel } from './user'
38
39 @Table({
40 tableName: 'account',
41 indexes: [
42 {
43 fields: [ 'name' ]
44 },
45 {
46 fields: [ 'serverId' ]
47 },
48 {
49 fields: [ 'userId' ],
50 unique: true
51 },
52 {
53 fields: [ 'applicationId' ],
54 unique: true
55 },
56 {
57 fields: [ 'name', 'serverId', 'applicationId' ],
58 unique: true
59 }
60 ]
61 })
62 export class AccountModel extends Model<Account> {
63
64 @AllowNull(false)
65 @Default(DataType.UUIDV4)
66 @IsUUID(4)
67 @Column(DataType.UUID)
68 uuid: string
69
70 @AllowNull(false)
71 @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
72 @Column
73 name: string
74
75 @AllowNull(false)
76 @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
77 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
78 url: string
79
80 @AllowNull(true)
81 @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key'))
82 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max))
83 publicKey: string
84
85 @AllowNull(true)
86 @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key'))
87 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max))
88 privateKey: string
89
90 @AllowNull(false)
91 @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count'))
92 @Column
93 followersCount: number
94
95 @AllowNull(false)
96 @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count'))
97 @Column
98 followingCount: number
99
100 @AllowNull(false)
101 @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
102 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
103 inboxUrl: string
104
105 @AllowNull(false)
106 @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
107 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
108 outboxUrl: string
109
110 @AllowNull(false)
111 @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
112 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
113 sharedInboxUrl: string
114
115 @AllowNull(false)
116 @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
117 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
118 followersUrl: string
119
120 @AllowNull(false)
121 @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
122 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
123 followingUrl: string
124
125 @CreatedAt
126 createdAt: Date
127
128 @UpdatedAt
129 updatedAt: Date
130
131 @ForeignKey(() => AvatarModel)
132 @Column
133 avatarId: number
134
135 @BelongsTo(() => AvatarModel, {
136 foreignKey: {
137 allowNull: true
138 },
139 onDelete: 'cascade'
140 })
141 Avatar: AvatarModel
142
143 @ForeignKey(() => ServerModel)
144 @Column
145 serverId: number
146
147 @BelongsTo(() => ServerModel, {
148 foreignKey: {
149 allowNull: true
150 },
151 onDelete: 'cascade'
152 })
153 Server: ServerModel
154
155 @ForeignKey(() => UserModel)
156 @Column
157 userId: number
158
159 @BelongsTo(() => UserModel, {
160 foreignKey: {
161 allowNull: true
162 },
163 onDelete: 'cascade'
164 })
165 User: UserModel
166
167 @ForeignKey(() => ApplicationModel)
168 @Column
169 applicationId: number
170
171 @BelongsTo(() => ApplicationModel, {
172 foreignKey: {
173 allowNull: true
174 },
175 onDelete: 'cascade'
176 })
177 Application: ApplicationModel
178
179 @HasMany(() => VideoChannelModel, {
180 foreignKey: {
181 allowNull: false
182 },
183 onDelete: 'cascade',
184 hooks: true
185 })
186 VideoChannels: VideoChannelModel[]
187
188 @HasMany(() => AccountFollowModel, {
189 foreignKey: {
190 name: 'accountId',
191 allowNull: false
192 },
193 onDelete: 'cascade'
194 })
195 AccountFollowing: AccountFollowModel[]
196
197 @HasMany(() => AccountFollowModel, {
198 foreignKey: {
199 name: 'targetAccountId',
200 allowNull: false
201 },
202 as: 'followers',
203 onDelete: 'cascade'
204 })
205 AccountFollowers: AccountFollowModel[]
206
207 @AfterDestroy
208 static sendDeleteIfOwned (instance: AccountModel) {
209 if (instance.isOwned()) {
210 return sendDeleteAccount(instance, undefined)
211 }
212
213 return undefined
214 }
215
216 static loadApplication () {
217 return AccountModel.findOne({
218 include: [
219 {
220 model: ApplicationModel,
221 required: true
222 }
223 ]
224 })
225 }
226
227 static load (id: number) {
228 return AccountModel.findById(id)
229 }
230
231 static loadByUUID (uuid: string) {
232 const query = {
233 where: {
234 uuid
235 }
236 }
237
238 return AccountModel.findOne(query)
239 }
240
241 static loadLocalByName (name: string) {
242 const query = {
243 where: {
244 name,
245 [ Sequelize.Op.or ]: [
246 {
247 userId: {
248 [ Sequelize.Op.ne ]: null
249 }
250 },
251 {
252 applicationId: {
253 [ Sequelize.Op.ne ]: null
254 }
255 }
256 ]
257 }
258 }
259
260 return AccountModel.findOne(query)
261 }
262
263 static loadByNameAndHost (name: string, host: string) {
264 const query = {
265 where: {
266 name
267 },
268 include: [
269 {
270 model: ServerModel,
271 required: true,
272 where: {
273 host
274 }
275 }
276 ]
277 }
278
279 return AccountModel.findOne(query)
280 }
281
282 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
283 const query = {
284 where: {
285 url
286 },
287 transaction
288 }
289
290 return AccountModel.findOne(query)
291 }
292
293 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
294 const query = {
295 where: {
296 followersUrl: {
297 [ Sequelize.Op.in ]: followersUrls
298 }
299 },
300 transaction
301 }
302
303 return AccountModel.findAll(query)
304 }
305
306 toFormattedJSON () {
307 let host = CONFIG.WEBSERVER.HOST
308 let score: number
309 let avatar: Avatar = null
310
311 if (this.Avatar) {
312 avatar = {
313 path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
314 createdAt: this.Avatar.createdAt,
315 updatedAt: this.Avatar.updatedAt
316 }
317 }
318
319 if (this.Server) {
320 host = this.Server.host
321 score = this.Server.score
322 }
323
324 return {
325 id: this.id,
326 uuid: this.uuid,
327 host,
328 score,
329 name: this.name,
330 followingCount: this.followingCount,
331 followersCount: this.followersCount,
332 createdAt: this.createdAt,
333 updatedAt: this.updatedAt,
334 avatar
335 }
336 }
337
338 toActivityPubObject () {
339 const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
340
341 const json = {
342 type,
343 id: this.url,
344 following: this.getFollowingUrl(),
345 followers: this.getFollowersUrl(),
346 inbox: this.inboxUrl,
347 outbox: this.outboxUrl,
348 preferredUsername: this.name,
349 url: this.url,
350 name: this.name,
351 endpoints: {
352 sharedInbox: this.sharedInboxUrl
353 },
354 uuid: this.uuid,
355 publicKey: {
356 id: this.getPublicKeyUrl(),
357 owner: this.url,
358 publicKeyPem: this.publicKey
359 }
360 }
361
362 return activityPubContextify(json)
363 }
364
365 isOwned () {
366 return this.serverId === null
367 }
368
369 getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
370 const query = {
371 attributes: [ 'sharedInboxUrl' ],
372 include: [
373 {
374 model: AccountFollowModel,
375 required: true,
376 as: 'followers',
377 where: {
378 targetAccountId: this.id
379 }
380 }
381 ],
382 transaction: t
383 }
384
385 return AccountModel.findAll(query)
386 .then(accounts => accounts.map(a => a.sharedInboxUrl))
387 }
388
389 getFollowingUrl () {
390 return this.url + '/following'
391 }
392
393 getFollowersUrl () {
394 return this.url + '/followers'
395 }
396
397 getPublicKeyUrl () {
398 return this.url + '#main-key'
399 }
400 }