]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user.ts
Add test to search in my videos
[github/Chocobozzz/PeerTube.git] / server / models / account / user.ts
CommitLineData
3acc5084 1import { FindOptions, literal, Op, QueryTypes } from 'sequelize'
3fd3ab2d 2import {
d175a6f7 3 AfterDestroy,
f201a749 4 AfterUpdate,
a73c582e
C
5 AllowNull,
6 BeforeCreate,
7 BeforeUpdate,
8 Column,
9 CreatedAt,
10 DataType,
11 Default,
12 DefaultScope,
13 HasMany,
14 HasOne,
15 Is,
16 IsEmail,
17 Model,
18 Scopes,
19 Table,
20 UpdatedAt
3fd3ab2d 21} from 'sequelize-typescript'
22a73cb8 22import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy } from '../../../shared'
ba75d268 23import { User, UserRole } from '../../../shared/models/users'
65fcc311 24import {
43d0ea7f 25 isNoInstanceConfigWarningModal,
1eddc9a7 26 isUserAdminFlagsValid,
a73c582e 27 isUserAutoPlayVideoValid,
6aa54148 28 isUserAutoPlayNextVideoValid,
bee29df8 29 isUserAutoPlayNextVideoPlaylistValid,
eacb25c4 30 isUserBlockedReasonValid,
e6921918 31 isUserBlockedValid,
d9eaee39 32 isUserEmailVerifiedValid,
5cf84858 33 isUserNSFWPolicyValid,
a73c582e
C
34 isUserPasswordValid,
35 isUserRoleValid,
36 isUserUsernameValid,
3caf77d3 37 isUserVideoLanguages,
5cf84858 38 isUserVideoQuotaDailyValid,
64cc5e85 39 isUserVideoQuotaValid,
cef534ed 40 isUserVideosHistoryEnabledValid,
43d0ea7f
C
41 isUserWebTorrentEnabledValid,
42 isNoWelcomeModal
3fd3ab2d 43} from '../../helpers/custom-validators/users'
da854ddd 44import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
3fd3ab2d
C
45import { OAuthTokenModel } from '../oauth/oauth-token'
46import { getSort, throwIfNotValid } from '../utils'
47import { VideoChannelModel } from '../video/video-channel'
48import { AccountModel } from './account'
0883b324
C
49import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
50import { values } from 'lodash'
ffb321be 51import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
f201a749 52import { clearCacheByUserId } from '../../lib/oauth-model'
cef534ed
C
53import { UserNotificationSettingModel } from './user-notification-setting'
54import { VideoModel } from '../video/video'
55import { ActorModel } from '../activitypub/actor'
56import { ActorFollowModel } from '../activitypub/actor-follow'
dc133480 57import { VideoImportModel } from '../video/video-import'
1eddc9a7 58import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
503c6f44 59import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
7cd4d2ba 60import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
453e83ea 61import * as Bluebird from 'bluebird'
1ca9f7c3
C
62import {
63 MUserDefault,
64 MUserFormattable,
65 MUserId,
66 MUserNotifSettingChannelDefault,
22a73cb8 67 MUserWithNotificationSetting, MVideoFullLight
1ca9f7c3 68} from '@server/typings/models'
3fd3ab2d 69
9c2e0dbf
C
70enum ScopeNames {
71 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
72}
73
3acc5084 74@DefaultScope(() => ({
d48ff09d
C
75 include: [
76 {
3acc5084 77 model: AccountModel,
d48ff09d 78 required: true
cef534ed
C
79 },
80 {
3acc5084 81 model: UserNotificationSettingModel,
cef534ed 82 required: true
d48ff09d
C
83 }
84 ]
3acc5084
C
85}))
86@Scopes(() => ({
9c2e0dbf 87 [ScopeNames.WITH_VIDEO_CHANNEL]: {
d48ff09d
C
88 include: [
89 {
3acc5084 90 model: AccountModel,
d48ff09d 91 required: true,
3acc5084 92 include: [ VideoChannelModel ]
cef534ed
C
93 },
94 {
3acc5084 95 model: UserNotificationSettingModel,
cef534ed 96 required: true
d48ff09d 97 }
3acc5084 98 ]
d48ff09d 99 }
3acc5084 100}))
3fd3ab2d
C
101@Table({
102 tableName: 'user',
103 indexes: [
feb4bdfd 104 {
3fd3ab2d
C
105 fields: [ 'username' ],
106 unique: true
feb4bdfd
C
107 },
108 {
3fd3ab2d
C
109 fields: [ 'email' ],
110 unique: true
feb4bdfd 111 }
e02643f3 112 ]
3fd3ab2d
C
113})
114export class UserModel extends Model<UserModel> {
115
116 @AllowNull(false)
117 @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password'))
118 @Column
119 password: string
120
121 @AllowNull(false)
122 @Is('UserPassword', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
123 @Column
124 username: string
125
126 @AllowNull(false)
127 @IsEmail
128 @Column(DataType.STRING(400))
129 email: string
130
d1ab89de
C
131 @AllowNull(true)
132 @IsEmail
133 @Column(DataType.STRING(400))
134 pendingEmail: string
135
d9eaee39
JM
136 @AllowNull(true)
137 @Default(null)
1735c825 138 @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true))
d9eaee39
JM
139 @Column
140 emailVerified: boolean
141
3fd3ab2d 142 @AllowNull(false)
0883b324 143 @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
1735c825 144 @Column(DataType.ENUM(...values(NSFW_POLICY_TYPES)))
0883b324 145 nsfwPolicy: NSFWPolicyType
3fd3ab2d 146
64cc5e85 147 @AllowNull(false)
0229b014 148 @Default(true)
ed638e53
RK
149 @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled'))
150 @Column
151 webTorrentEnabled: boolean
64cc5e85 152
8b9a525a
C
153 @AllowNull(false)
154 @Default(true)
155 @Is('UserVideosHistoryEnabled', value => throwIfNotValid(value, isUserVideosHistoryEnabledValid, 'Videos history enabled'))
156 @Column
157 videosHistoryEnabled: boolean
158
7efe153b
AL
159 @AllowNull(false)
160 @Default(true)
161 @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
162 @Column
163 autoPlayVideo: boolean
164
6aa54148
L
165 @AllowNull(false)
166 @Default(false)
167 @Is('UserAutoPlayNextVideo', value => throwIfNotValid(value, isUserAutoPlayNextVideoValid, 'auto play next video boolean'))
168 @Column
169 autoPlayNextVideo: boolean
170
bee29df8
RK
171 @AllowNull(false)
172 @Default(true)
173 @Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean'))
174 @Column
175 autoPlayNextVideoPlaylist: boolean
176
3caf77d3
C
177 @AllowNull(true)
178 @Default(null)
179 @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
180 @Column(DataType.ARRAY(DataType.STRING))
181 videoLanguages: string[]
182
1eddc9a7
C
183 @AllowNull(false)
184 @Default(UserAdminFlag.NONE)
185 @Is('UserAdminFlags', value => throwIfNotValid(value, isUserAdminFlagsValid, 'user admin flags'))
186 @Column
187 adminFlags?: UserAdminFlag
188
e6921918
C
189 @AllowNull(false)
190 @Default(false)
191 @Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean'))
192 @Column
193 blocked: boolean
194
eacb25c4
C
195 @AllowNull(true)
196 @Default(null)
1735c825 197 @Is('UserBlockedReason', value => throwIfNotValid(value, isUserBlockedReasonValid, 'blocked reason', true))
eacb25c4
C
198 @Column
199 blockedReason: string
200
3fd3ab2d
C
201 @AllowNull(false)
202 @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
203 @Column
204 role: number
205
206 @AllowNull(false)
207 @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota'))
208 @Column(DataType.BIGINT)
209 videoQuota: number
210
bee0abff
FA
211 @AllowNull(false)
212 @Is('UserVideoQuotaDaily', value => throwIfNotValid(value, isUserVideoQuotaDailyValid, 'video quota daily'))
213 @Column(DataType.BIGINT)
214 videoQuotaDaily: number
215
7cd4d2ba 216 @AllowNull(false)
ffb321be 217 @Default(DEFAULT_THEME_NAME)
503c6f44 218 @Is('UserTheme', value => throwIfNotValid(value, isThemeNameValid, 'theme'))
7cd4d2ba
C
219 @Column
220 theme: string
221
43d0ea7f
C
222 @AllowNull(false)
223 @Default(false)
224 @Is(
225 'UserNoInstanceConfigWarningModal',
226 value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal')
227 )
228 @Column
229 noInstanceConfigWarningModal: boolean
230
231 @AllowNull(false)
232 @Default(false)
233 @Is(
234 'UserNoInstanceConfigWarningModal',
235 value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal')
236 )
237 @Column
238 noWelcomeModal: boolean
239
3fd3ab2d
C
240 @CreatedAt
241 createdAt: Date
242
243 @UpdatedAt
244 updatedAt: Date
245
246 @HasOne(() => AccountModel, {
247 foreignKey: 'userId',
f05a1c30
C
248 onDelete: 'cascade',
249 hooks: true
3fd3ab2d
C
250 })
251 Account: AccountModel
69b0a27c 252
cef534ed
C
253 @HasOne(() => UserNotificationSettingModel, {
254 foreignKey: 'userId',
255 onDelete: 'cascade',
256 hooks: true
257 })
258 NotificationSetting: UserNotificationSettingModel
259
dc133480
C
260 @HasMany(() => VideoImportModel, {
261 foreignKey: 'userId',
262 onDelete: 'cascade'
263 })
264 VideoImports: VideoImportModel[]
265
3fd3ab2d
C
266 @HasMany(() => OAuthTokenModel, {
267 foreignKey: 'userId',
268 onDelete: 'cascade'
269 })
270 OAuthTokens: OAuthTokenModel[]
271
272 @BeforeCreate
273 @BeforeUpdate
274 static cryptPasswordIfNeeded (instance: UserModel) {
275 if (instance.changed('password')) {
276 return cryptPassword(instance.password)
277 .then(hash => {
278 instance.password = hash
279 return undefined
280 })
281 }
59557c46 282 }
26d7d31b 283
f201a749 284 @AfterUpdate
d175a6f7 285 @AfterDestroy
f201a749
C
286 static removeTokenCache (instance: UserModel) {
287 return clearCacheByUserId(instance.id)
288 }
289
3fd3ab2d
C
290 static countTotal () {
291 return this.count()
292 }
954605a8 293
24b9417c
C
294 static listForApi (start: number, count: number, sort: string, search?: string) {
295 let where = undefined
296 if (search) {
297 where = {
3acc5084 298 [Op.or]: [
24b9417c
C
299 {
300 email: {
3acc5084 301 [Op.iLike]: '%' + search + '%'
24b9417c
C
302 }
303 },
304 {
305 username: {
3acc5084 306 [ Op.iLike ]: '%' + search + '%'
24b9417c
C
307 }
308 }
309 ]
310 }
311 }
312
3acc5084 313 const query: FindOptions = {
a76138ff
C
314 attributes: {
315 include: [
316 [
3acc5084 317 literal(
a76138ff 318 '(' +
8b604880
C
319 'SELECT COALESCE(SUM("size"), 0) ' +
320 'FROM (' +
a76138ff
C
321 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
322 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
323 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
324 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
325 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' +
326 ') t' +
327 ')'
328 ),
329 'videoQuotaUsed'
3acc5084 330 ]
a76138ff
C
331 ]
332 },
3fd3ab2d
C
333 offset: start,
334 limit: count,
24b9417c
C
335 order: getSort(sort),
336 where
3fd3ab2d 337 }
72c7248b 338
3fd3ab2d
C
339 return UserModel.findAndCountAll(query)
340 .then(({ rows, count }) => {
341 return {
342 data: rows,
343 total: count
344 }
72c7248b 345 })
72c7248b
C
346 }
347
453e83ea 348 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
ba75d268
C
349 const roles = Object.keys(USER_ROLE_LABELS)
350 .map(k => parseInt(k, 10) as UserRole)
351 .filter(role => hasUserRight(role, right))
352
ba75d268 353 const query = {
ba75d268
C
354 where: {
355 role: {
3acc5084 356 [Op.in]: roles
ba75d268
C
357 }
358 }
359 }
360
cef534ed
C
361 return UserModel.findAll(query)
362 }
363
453e83ea 364 static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> {
cef534ed
C
365 const query = {
366 include: [
367 {
368 model: UserNotificationSettingModel.unscoped(),
369 required: true
370 },
371 {
372 attributes: [ 'userId' ],
373 model: AccountModel.unscoped(),
374 required: true,
375 include: [
376 {
377 attributes: [ ],
378 model: ActorModel.unscoped(),
379 required: true,
380 where: {
381 serverId: null
382 },
383 include: [
384 {
385 attributes: [ ],
386 as: 'ActorFollowings',
387 model: ActorFollowModel.unscoped(),
388 required: true,
389 where: {
390 targetActorId: actorId
391 }
392 }
393 ]
394 }
395 ]
396 }
397 ]
398 }
399
400 return UserModel.unscoped().findAll(query)
ba75d268
C
401 }
402
453e83ea 403 static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> {
f7cc67b4
C
404 const query = {
405 where: {
406 username: usernames
407 }
408 }
409
410 return UserModel.findAll(query)
411 }
412
453e83ea 413 static loadById (id: number): Bluebird<MUserDefault> {
9b39106d 414 return UserModel.findByPk(id)
3fd3ab2d 415 }
feb4bdfd 416
453e83ea 417 static loadByUsername (username: string): Bluebird<MUserDefault> {
3fd3ab2d
C
418 const query = {
419 where: {
50b4dcce 420 username: { [ Op.iLike ]: username }
d48ff09d 421 }
3fd3ab2d 422 }
089ff2f2 423
3fd3ab2d 424 return UserModel.findOne(query)
feb4bdfd
C
425 }
426
0283eaac 427 static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> {
3fd3ab2d
C
428 const query = {
429 where: {
50b4dcce 430 username: { [ Op.iLike ]: username }
d48ff09d 431 }
3fd3ab2d 432 }
9bd26629 433
9c2e0dbf 434 return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query)
feb4bdfd
C
435 }
436
453e83ea 437 static loadByEmail (email: string): Bluebird<MUserDefault> {
ecb4e35f
C
438 const query = {
439 where: {
440 email
441 }
442 }
443
444 return UserModel.findOne(query)
445 }
446
453e83ea 447 static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> {
ba12e8b3
C
448 if (!email) email = username
449
3fd3ab2d 450 const query = {
3fd3ab2d 451 where: {
50b4dcce 452 [ Op.or ]: [ { username: { [ Op.iLike ]: username } }, { email } ]
3fd3ab2d 453 }
6fcd19ba 454 }
69b0a27c 455
d48ff09d 456 return UserModel.findOne(query)
72c7248b
C
457 }
458
453e83ea 459 static loadByVideoId (videoId: number): Bluebird<MUserDefault> {
cef534ed
C
460 const query = {
461 include: [
462 {
463 required: true,
464 attributes: [ 'id' ],
465 model: AccountModel.unscoped(),
466 include: [
467 {
468 required: true,
469 attributes: [ 'id' ],
470 model: VideoChannelModel.unscoped(),
471 include: [
472 {
473 required: true,
474 attributes: [ 'id' ],
475 model: VideoModel.unscoped(),
476 where: {
477 id: videoId
478 }
479 }
480 ]
481 }
482 ]
483 }
484 ]
485 }
486
487 return UserModel.findOne(query)
488 }
489
453e83ea 490 static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> {
dc133480
C
491 const query = {
492 include: [
493 {
494 required: true,
495 attributes: [ 'id' ],
496 model: VideoImportModel.unscoped(),
497 where: {
498 id: videoImportId
499 }
500 }
501 ]
502 }
503
504 return UserModel.findOne(query)
505 }
506
453e83ea 507 static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> {
f7cc67b4
C
508 const query = {
509 include: [
510 {
511 required: true,
512 attributes: [ 'id' ],
513 model: AccountModel.unscoped(),
514 include: [
515 {
516 required: true,
517 attributes: [ 'id' ],
518 model: VideoChannelModel.unscoped(),
519 where: {
520 actorId: videoChannelActorId
521 }
522 }
523 ]
524 }
525 ]
526 }
527
528 return UserModel.findOne(query)
529 }
530
453e83ea 531 static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> {
f7cc67b4
C
532 const query = {
533 include: [
534 {
535 required: true,
536 attributes: [ 'id' ],
537 model: AccountModel.unscoped(),
538 where: {
539 actorId: accountActorId
540 }
541 }
542 ]
543 }
544
545 return UserModel.findOne(query)
546 }
547
453e83ea 548 static getOriginalVideoFileTotalFromUser (user: MUserId) {
3fd3ab2d 549 // Don't use sequelize because we need to use a sub query
8b604880 550 const query = UserModel.generateUserQuotaBaseSQL()
bee0abff 551
8b604880 552 return UserModel.getTotalRawQuery(query, user.id)
bee0abff
FA
553 }
554
8b604880 555 // Returns cumulative size of all video files uploaded in the last 24 hours.
453e83ea 556 static getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
bee0abff 557 // Don't use sequelize because we need to use a sub query
8b604880 558 const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'')
68a3b9f2 559
8b604880 560 return UserModel.getTotalRawQuery(query, user.id)
72c7248b
C
561 }
562
09cababd
C
563 static async getStats () {
564 const totalUsers = await UserModel.count()
565
566 return {
567 totalUsers
568 }
569 }
570
5cf84858
C
571 static autoComplete (search: string) {
572 const query = {
573 where: {
574 username: {
3acc5084 575 [ Op.like ]: `%${search}%`
5cf84858
C
576 }
577 },
578 limit: 10
579 }
580
581 return UserModel.findAll(query)
582 .then(u => u.map(u => u.username))
583 }
584
22a73cb8 585 canGetVideo (video: MVideoFullLight) {
2a5518a6 586 const videoUserId = video.VideoChannel.Account.userId
22a73cb8 587
2a5518a6
C
588 if (video.isBlacklisted()) {
589 return videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
22a73cb8
C
590 }
591
2a5518a6
C
592 if (video.privacy === VideoPrivacy.PRIVATE) {
593 return video.VideoChannel && videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
22a73cb8
C
594 }
595
2a5518a6
C
596 if (video.privacy === VideoPrivacy.INTERNAL) return true
597
22a73cb8
C
598 return false
599 }
600
3fd3ab2d
C
601 hasRight (right: UserRight) {
602 return hasUserRight(this.role, right)
603 }
72c7248b 604
1eddc9a7
C
605 hasAdminFlag (flag: UserAdminFlag) {
606 return this.adminFlags & flag
607 }
608
3fd3ab2d
C
609 isPasswordMatch (password: string) {
610 return comparePassword(password, this.password)
feb4bdfd
C
611 }
612
1ca9f7c3 613 toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
a76138ff 614 const videoQuotaUsed = this.get('videoQuotaUsed')
bee0abff 615 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
a76138ff 616
43d0ea7f 617 const json: User = {
3fd3ab2d
C
618 id: this.id,
619 username: this.username,
620 email: this.email,
43d0ea7f
C
621 theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME),
622
d1ab89de 623 pendingEmail: this.pendingEmail,
d9eaee39 624 emailVerified: this.emailVerified,
43d0ea7f 625
0883b324 626 nsfwPolicy: this.nsfwPolicy,
ed638e53 627 webTorrentEnabled: this.webTorrentEnabled,
276d9652 628 videosHistoryEnabled: this.videosHistoryEnabled,
7efe153b 629 autoPlayVideo: this.autoPlayVideo,
6aa54148 630 autoPlayNextVideo: this.autoPlayNextVideo,
bee29df8 631 autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist,
3caf77d3 632 videoLanguages: this.videoLanguages,
43d0ea7f 633
3fd3ab2d
C
634 role: this.role,
635 roleLabel: USER_ROLE_LABELS[ this.role ],
43d0ea7f 636
3fd3ab2d 637 videoQuota: this.videoQuota,
bee0abff 638 videoQuotaDaily: this.videoQuotaDaily,
43d0ea7f
C
639 videoQuotaUsed: videoQuotaUsed !== undefined
640 ? parseInt(videoQuotaUsed + '', 10)
641 : undefined,
642 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
643 ? parseInt(videoQuotaUsedDaily + '', 10)
644 : undefined,
645
646 noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
647 noWelcomeModal: this.noWelcomeModal,
648
eacb25c4
C
649 blocked: this.blocked,
650 blockedReason: this.blockedReason,
43d0ea7f 651
c5911fd3 652 account: this.Account.toFormattedJSON(),
43d0ea7f
C
653
654 notificationSettings: this.NotificationSetting
655 ? this.NotificationSetting.toFormattedJSON()
656 : undefined,
657
a76138ff 658 videoChannels: [],
43d0ea7f
C
659
660 createdAt: this.createdAt
3fd3ab2d
C
661 }
662
1eddc9a7
C
663 if (parameters.withAdminFlags) {
664 Object.assign(json, { adminFlags: this.adminFlags })
665 }
666
3fd3ab2d 667 if (Array.isArray(this.Account.VideoChannels) === true) {
c5911fd3 668 json.videoChannels = this.Account.VideoChannels
3fd3ab2d
C
669 .map(c => c.toFormattedJSON())
670 .sort((v1, v2) => {
671 if (v1.createdAt < v2.createdAt) return -1
672 if (v1.createdAt === v2.createdAt) return 0
ad4a8a1c 673
3fd3ab2d
C
674 return 1
675 })
ad4a8a1c 676 }
3fd3ab2d
C
677
678 return json
ad4a8a1c
C
679 }
680
bee0abff
FA
681 async isAbleToUploadVideo (videoFile: { size: number }) {
682 if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true)
b0f9f39e 683
bee0abff
FA
684 const [ totalBytes, totalBytesDaily ] = await Promise.all([
685 UserModel.getOriginalVideoFileTotalFromUser(this),
686 UserModel.getOriginalVideoFileTotalDailyFromUser(this)
687 ])
688
689 const uploadedTotal = videoFile.size + totalBytes
690 const uploadedDaily = videoFile.size + totalBytesDaily
bee0abff 691
3acc5084
C
692 if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota
693 if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily
694
695 return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily
b0f9f39e 696 }
8b604880
C
697
698 private static generateUserQuotaBaseSQL (where?: string) {
699 const andWhere = where ? 'AND ' + where : ''
700
701 return 'SELECT SUM("size") AS "total" ' +
702 'FROM (' +
703 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
704 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
705 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
706 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
707 'WHERE "account"."userId" = $userId ' + andWhere +
708 'GROUP BY "video"."id"' +
709 ') t'
710 }
711
712 private static getTotalRawQuery (query: string, userId: number) {
713 const options = {
714 bind: { userId },
3acc5084 715 type: QueryTypes.SELECT as QueryTypes.SELECT
8b604880
C
716 }
717
3acc5084 718 return UserModel.sequelize.query<{ total: string }>(query, options)
8b604880
C
719 .then(([ { total } ]) => {
720 if (total === null) return 0
721
3acc5084 722 return parseInt(total, 10)
8b604880
C
723 })
724 }
b0f9f39e 725}