]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/account.ts
Add video file size info in admin videos list
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
CommitLineData
b49f22d8 1import { FindOptions, Includeable, IncludeOptions, Op, Transaction, WhereOptions } from 'sequelize'
3fd3ab2d 2import {
2422c46b
C
3 AllowNull,
4 BeforeDestroy,
5 BelongsTo,
6 Column,
453e83ea
C
7 CreatedAt,
8 DataType,
2422c46b
C
9 Default,
10 DefaultScope,
11 ForeignKey,
12 HasMany,
13 Is,
09979f89
C
14 Model,
15 Scopes,
2422c46b 16 Table,
3fd3ab2d
C
17 UpdatedAt
18} from 'sequelize-typescript'
a59f210f 19import { ModelCache } from '@server/models/model-cache'
16c016e8 20import { AttributesOnly } from '@shared/core-utils'
418d092a 21import { Account, AccountSummary } from '../../../shared/models/actors'
2422c46b 22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
a59f210f 23import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants'
c158a5fa 24import { sendDeleteActor } from '../../lib/activitypub/send/send-delete'
a59f210f
C
25import {
26 MAccount,
27 MAccountActor,
28 MAccountAP,
29 MAccountDefault,
30 MAccountFormattable,
31 MAccountSummaryFormattable,
32 MChannelActor
33} from '../../types/models'
7d9ba5c0
C
34import { ActorModel } from '../actor/actor'
35import { ActorFollowModel } from '../actor/actor-follow'
36import { ActorImageModel } from '../actor/actor-image'
3fd3ab2d 37import { ApplicationModel } from '../application/application'
3fd3ab2d 38import { ServerModel } from '../server/server'
a59f210f 39import { ServerBlocklistModel } from '../server/server-blocklist'
7d9ba5c0 40import { UserModel } from '../user/user'
2422c46b 41import { getSort, throwIfNotValid } from '../utils'
a59f210f 42import { VideoModel } from '../video/video'
3fd3ab2d 43import { VideoChannelModel } from '../video/video-channel'
f05a1c30 44import { VideoCommentModel } from '../video/video-comment'
418d092a 45import { VideoPlaylistModel } from '../video/video-playlist'
bfbd9128 46import { AccountBlocklistModel } from './account-blocklist'
418d092a
C
47
48export enum ScopeNames {
49 SUMMARY = 'SUMMARY'
50}
3fd3ab2d 51
bfbd9128 52export type SummaryOptions = {
4f32032f 53 actorRequired?: boolean // Default: true
bfbd9128 54 whereActor?: WhereOptions
fa47956e 55 whereServer?: WhereOptions
bfbd9128
C
56 withAccountBlockerIds?: number[]
57}
58
3acc5084 59@DefaultScope(() => ({
50d6de9c 60 include: [
3fd3ab2d 61 {
3acc5084 62 model: ActorModel, // Default scope includes avatar and server
f37dc0dd 63 required: true
e4f97bab 64 }
e4f97bab 65 ]
3acc5084
C
66}))
67@Scopes(() => ({
a1587156 68 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
bfbd9128
C
69 const serverInclude: IncludeOptions = {
70 attributes: [ 'host' ],
71 model: ServerModel.unscoped(),
fa47956e
C
72 required: !!options.whereServer,
73 where: options.whereServer
bfbd9128
C
74 }
75
b49f22d8
C
76 const queryInclude: Includeable[] = [
77 {
78 attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
79 model: ActorModel.unscoped(),
80 required: options.actorRequired ?? true,
fa47956e 81 where: options.whereActor,
b49f22d8
C
82 include: [
83 serverInclude,
bfbd9128 84
b49f22d8 85 {
f4796856
C
86 model: ActorImageModel.unscoped(),
87 as: 'Avatar',
b49f22d8
C
88 required: false
89 }
90 ]
91 }
92 ]
93
94 const query: FindOptions = {
95 attributes: [ 'id', 'name', 'actorId' ]
418d092a 96 }
bfbd9128
C
97
98 if (options.withAccountBlockerIds) {
b49f22d8 99 queryInclude.push({
bfbd9128
C
100 attributes: [ 'id' ],
101 model: AccountBlocklistModel.unscoped(),
3c10840f 102 as: 'BlockedBy',
bfbd9128
C
103 required: false,
104 where: {
105 accountId: {
106 [Op.in]: options.withAccountBlockerIds
107 }
108 }
109 })
110
111 serverInclude.include = [
112 {
113 attributes: [ 'id' ],
114 model: ServerBlocklistModel.unscoped(),
115 required: false,
116 where: {
117 accountId: {
118 [Op.in]: options.withAccountBlockerIds
119 }
120 }
121 }
122 ]
123 }
124
b49f22d8
C
125 query.include = queryInclude
126
bfbd9128 127 return query
418d092a 128 }
3acc5084 129}))
50d6de9c 130@Table({
8cd72bd3
C
131 tableName: 'account',
132 indexes: [
133 {
134 fields: [ 'actorId' ],
135 unique: true
136 },
137 {
138 fields: [ 'applicationId' ]
139 },
140 {
141 fields: [ 'userId' ]
142 }
143 ]
50d6de9c 144})
16c016e8 145export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
3fd3ab2d 146
50d6de9c 147 @AllowNull(false)
50d6de9c
C
148 @Column
149 name: string
150
2422c46b
C
151 @AllowNull(true)
152 @Default(null)
1735c825 153 @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description', true))
241c3357 154 @Column(DataType.STRING(CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max))
2422c46b
C
155 description: string
156
3fd3ab2d
C
157 @CreatedAt
158 createdAt: Date
159
160 @UpdatedAt
161 updatedAt: Date
162
fadf619a 163 @ForeignKey(() => ActorModel)
3fd3ab2d 164 @Column
fadf619a 165 actorId: number
e4f97bab 166
fadf619a 167 @BelongsTo(() => ActorModel, {
e4f97bab 168 foreignKey: {
fadf619a 169 allowNull: false
e4f97bab
C
170 },
171 onDelete: 'cascade'
172 })
fadf619a 173 Actor: ActorModel
e4f97bab 174
3fd3ab2d
C
175 @ForeignKey(() => UserModel)
176 @Column
177 userId: number
178
179 @BelongsTo(() => UserModel, {
e4f97bab 180 foreignKey: {
e4f97bab
C
181 allowNull: true
182 },
183 onDelete: 'cascade'
184 })
3fd3ab2d
C
185 User: UserModel
186
187 @ForeignKey(() => ApplicationModel)
188 @Column
189 applicationId: number
e4f97bab 190
3fd3ab2d 191 @BelongsTo(() => ApplicationModel, {
e4f97bab 192 foreignKey: {
e4f97bab
C
193 allowNull: true
194 },
195 onDelete: 'cascade'
196 })
f05a1c30 197 Application: ApplicationModel
e4f97bab 198
3fd3ab2d 199 @HasMany(() => VideoChannelModel, {
e4f97bab 200 foreignKey: {
e4f97bab
C
201 allowNull: false
202 },
203 onDelete: 'cascade',
204 hooks: true
205 })
3fd3ab2d 206 VideoChannels: VideoChannelModel[]
e4f97bab 207
418d092a
C
208 @HasMany(() => VideoPlaylistModel, {
209 foreignKey: {
210 allowNull: false
211 },
212 onDelete: 'cascade',
213 hooks: true
214 })
215 VideoPlaylists: VideoPlaylistModel[]
216
f05a1c30
C
217 @HasMany(() => VideoCommentModel, {
218 foreignKey: {
69222afa 219 allowNull: true
f05a1c30
C
220 },
221 onDelete: 'cascade',
222 hooks: true
223 })
224 VideoComments: VideoCommentModel[]
225
bfbd9128
C
226 @HasMany(() => AccountBlocklistModel, {
227 foreignKey: {
228 name: 'targetAccountId',
229 allowNull: false
230 },
2760b454 231 as: 'BlockedBy',
bfbd9128
C
232 onDelete: 'CASCADE'
233 })
2760b454 234 BlockedBy: AccountBlocklistModel[]
bfbd9128 235
f05a1c30
C
236 @BeforeDestroy
237 static async sendDeleteIfOwned (instance: AccountModel, options) {
238 if (!instance.Actor) {
e6122097 239 instance.Actor = await instance.$get('Actor', { transaction: options.transaction })
f05a1c30
C
240 }
241
44b88f18 242 await ActorFollowModel.removeFollowsOf(instance.Actor.id, options.transaction)
2af337c8 243
c5a893d5 244 if (instance.isOwned()) {
c5a893d5
C
245 return sendDeleteActor(instance.Actor, options.transaction)
246 }
247
248 return undefined
e4f97bab
C
249 }
250
b49f22d8 251 static load (id: number, transaction?: Transaction): Promise<MAccountDefault> {
9b39106d 252 return AccountModel.findByPk(id, { transaction })
3fd3ab2d 253 }
2295ce6c 254
b49f22d8 255 static loadByNameWithHost (nameWithHost: string): Promise<MAccountDefault> {
92bf2f62
C
256 const [ accountName, host ] = nameWithHost.split('@')
257
6dd9de95 258 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName)
92bf2f62
C
259
260 return AccountModel.loadByNameAndHost(accountName, host)
261 }
262
b49f22d8 263 static loadLocalByName (name: string): Promise<MAccountDefault> {
0ffd6d32
C
264 const fun = () => {
265 const query = {
266 where: {
267 [Op.or]: [
268 {
269 userId: {
270 [Op.ne]: null
271 }
272 },
273 {
274 applicationId: {
275 [Op.ne]: null
276 }
3fd3ab2d 277 }
0ffd6d32
C
278 ]
279 },
280 include: [
3fd3ab2d 281 {
0ffd6d32
C
282 model: ActorModel,
283 required: true,
284 where: {
285 preferredUsername: name
3fd3ab2d
C
286 }
287 }
288 ]
0ffd6d32 289 }
e8cb4409 290
0ffd6d32
C
291 return AccountModel.findOne(query)
292 }
e4a686b4 293
0ffd6d32
C
294 return ModelCache.Instance.doCache({
295 cacheType: 'local-account-name',
296 key: name,
297 fun,
298 // The server actor never change, so we can easily cache it
299 whitelist: () => name === SERVER_ACTOR_NAME
300 })
e8cb4409
C
301 }
302
b49f22d8 303 static loadByNameAndHost (name: string, host: string): Promise<MAccountDefault> {
e8cb4409
C
304 const query = {
305 include: [
306 {
307 model: ActorModel,
308 required: true,
309 where: {
310 preferredUsername: name
311 },
312 include: [
313 {
314 model: ServerModel,
315 required: true,
316 where: {
317 host
318 }
319 }
320 ]
321 }
322 ]
3fd3ab2d 323 }
7a7724e6 324
3fd3ab2d
C
325 return AccountModel.findOne(query)
326 }
7a7724e6 327
b49f22d8 328 static loadByUrl (url: string, transaction?: Transaction): Promise<MAccountDefault> {
3fd3ab2d 329 const query = {
fadf619a
C
330 include: [
331 {
332 model: ActorModel,
333 required: true,
334 where: {
335 url
336 }
337 }
338 ],
3fd3ab2d
C
339 transaction
340 }
e4f97bab 341
3fd3ab2d
C
342 return AccountModel.findOne(query)
343 }
e4f97bab 344
265ba139
C
345 static listForApi (start: number, count: number, sort: string) {
346 const query = {
347 offset: start,
348 limit: count,
6ff9c676 349 order: getSort(sort)
265ba139
C
350 }
351
352 return AccountModel.findAndCountAll(query)
c5a893d5
C
353 .then(({ rows, count }) => {
354 return {
355 data: rows,
356 total: count
357 }
358 })
265ba139
C
359 }
360
b49f22d8 361 static loadAccountIdFromVideo (videoId: number): Promise<MAccount> {
696d83fd
C
362 const query = {
363 include: [
364 {
365 attributes: [ 'id', 'accountId' ],
366 model: VideoChannelModel.unscoped(),
367 required: true,
368 include: [
369 {
370 attributes: [ 'id', 'channelId' ],
371 model: VideoModel.unscoped(),
372 where: {
373 id: videoId
374 }
375 }
376 ]
377 }
378 ]
379 }
380
381 return AccountModel.findOne(query)
382 }
383
b49f22d8 384 static listLocalsForSitemap (sort: string): Promise<MAccountActor[]> {
2feebf3e
C
385 const query = {
386 attributes: [ ],
387 offset: 0,
388 order: getSort(sort),
389 include: [
390 {
391 attributes: [ 'preferredUsername', 'serverId' ],
392 model: ActorModel.unscoped(),
393 where: {
394 serverId: null
395 }
396 }
397 ]
398 }
399
400 return AccountModel
401 .unscoped()
402 .findAll(query)
403 }
404
d95d1559
C
405 getClientUrl () {
406 return WEBSERVER.URL + '/accounts/' + this.Actor.getIdentifier()
407 }
408
1ca9f7c3 409 toFormattedJSON (this: MAccountFormattable): Account {
fadf619a
C
410 const actor = this.Actor.toFormattedJSON()
411 const account = {
3fd3ab2d 412 id: this.id,
244e76a5 413 displayName: this.getDisplayName(),
2422c46b 414 description: this.description,
e024fd6a 415 updatedAt: this.updatedAt,
79bd2632 416 userId: this.userId ? this.userId : undefined
3fd3ab2d 417 }
fadf619a
C
418
419 return Object.assign(actor, account)
3fd3ab2d 420 }
e4f97bab 421
1ca9f7c3
C
422 toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary {
423 const actor = this.Actor.toFormattedSummaryJSON()
418d092a
C
424
425 return {
426 id: this.id,
418d092a
C
427 name: actor.name,
428 displayName: this.getDisplayName(),
429 url: actor.url,
430 host: actor.host,
431 avatar: actor.avatar
432 }
433 }
434
b5fecbf4 435 toActivityPubObject (this: MAccountAP) {
8424c402 436 const obj = this.Actor.toActivityPubObject(this.name)
2422c46b
C
437
438 return Object.assign(obj, {
439 summary: this.description
440 })
e4f97bab
C
441 }
442
3fd3ab2d 443 isOwned () {
fadf619a 444 return this.Actor.isOwned()
3fd3ab2d 445 }
244e76a5 446
744d0eca
C
447 isOutdated () {
448 return this.Actor.isOutdated()
449 }
450
244e76a5
RK
451 getDisplayName () {
452 return this.name
453 }
bfbd9128 454
a59f210f
C
455 getLocalUrl (this: MAccountActor | MChannelActor) {
456 return WEBSERVER.URL + `/accounts/` + this.Actor.preferredUsername
457 }
458
bfbd9128 459 isBlocked () {
2760b454 460 return this.BlockedBy && this.BlockedBy.length !== 0
bfbd9128 461 }
63c93323 462}