-import { values } from 'lodash'
import { col, FindOptions, fn, literal, Op, QueryTypes, where, WhereOptions } from 'sequelize'
import {
AfterDestroy,
UpdatedAt
} from 'sequelize-typescript'
import { TokensCache } from '@server/lib/auth/tokens-cache'
+import { LiveQuotaStore } from '@server/lib/live'
import {
MMyUserFormattable,
MUser,
MUserDefault,
MUserFormattable,
MUserNotifSettingChannelDefault,
- MUserWithNotificationSetting,
- MVideoWithRights
+ MUserWithNotificationSetting
} from '@server/types/models'
+import { forceNumber } from '@shared/core-utils'
import { AttributesOnly } from '@shared/typescript-utils'
import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
-import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models'
+import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models'
import { User, UserRole } from '../../../shared/models/users'
import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
isUserEmailVerifiedValid,
isUserNoModal,
isUserNSFWPolicyValid,
+ isUserP2PEnabledValid,
isUserPasswordValid,
isUserRoleValid,
- isUserUsernameValid,
isUserVideoLanguages,
isUserVideoQuotaDailyValid,
isUserVideoQuotaValid,
- isUserVideosHistoryEnabledValid,
- isUserP2PEnabledValid
+ isUserVideosHistoryEnabledValid
} from '../../helpers/custom-validators/users'
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
import { ActorFollowModel } from '../actor/actor-follow'
import { ActorImageModel } from '../actor/actor-image'
import { OAuthTokenModel } from '../oauth/oauth-token'
-import { getSort, throwIfNotValid } from '../utils'
+import { getAdminUsersSort, throwIfNotValid } from '../shared'
import { VideoModel } from '../video/video'
import { VideoChannelModel } from '../video/video-channel'
import { VideoImportModel } from '../video/video-import'
enum ScopeNames {
FOR_ME_API = 'FOR_ME_API',
WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS',
+ WITH_QUOTA = 'WITH_QUOTA',
WITH_STATS = 'WITH_STATS'
}
include: [
{
model: ActorImageModel,
- as: 'Banner',
+ as: 'Banners',
required: false
}
]
}
]
},
- [ScopeNames.WITH_STATS]: {
+ [ScopeNames.WITH_QUOTA]: {
attributes: {
include: [
[
'(' +
UserModel.generateUserQuotaBaseSQL({
withSelect: false,
- whereUserId: '"UserModel"."id"'
+ whereUserId: '"UserModel"."id"',
+ daily: false
}) +
')'
),
'videoQuotaUsed'
],
+ [
+ literal(
+ '(' +
+ UserModel.generateUserQuotaBaseSQL({
+ withSelect: false,
+ whereUserId: '"UserModel"."id"',
+ daily: true
+ }) +
+ ')'
+ ),
+ 'videoQuotaUsedDaily'
+ ]
+ ]
+ }
+ },
+ [ScopeNames.WITH_STATS]: {
+ attributes: {
+ include: [
[
literal(
'(' +
password: string
@AllowNull(false)
- @Is('UserUsername', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
@Column
username: string
@AllowNull(false)
@Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
- @Column(DataType.ENUM(...values(NSFW_POLICY_TYPES)))
+ @Column(DataType.ENUM(...Object.values(NSFW_POLICY_TYPES)))
nsfwPolicy: NSFWPolicyType
@AllowNull(false)
@Column
lastLoginDate: Date
+ @AllowNull(false)
+ @Default(false)
+ @Column
+ emailPublic: boolean
+
+ @AllowNull(true)
+ @Default(null)
+ @Column
+ otpSecret: string
+
@CreatedAt
createdAt: Date
})
OAuthTokens: OAuthTokenModel[]
+ // Used if we already set an encrypted password in user model
+ skipPasswordEncryption = false
+
@BeforeCreate
@BeforeUpdate
- static cryptPasswordIfNeeded (instance: UserModel) {
- if (instance.changed('password') && instance.password) {
- return cryptPassword(instance.password)
- .then(hash => {
- instance.password = hash
- return undefined
- })
- }
+ static async cryptPasswordIfNeeded (instance: UserModel) {
+ if (instance.skipPasswordEncryption) return
+ if (!instance.changed('password')) return
+ if (!instance.password) return
+
+ instance.password = await cryptPassword(instance.password)
}
@AfterUpdate
}
static countTotal () {
- return this.count()
+ return UserModel.unscoped().count()
}
- static listForApi (parameters: {
+ static listForAdminApi (parameters: {
start: number
count: number
sort: string
}
if (blocked !== undefined) {
- Object.assign(where, {
- blocked: blocked
- })
+ Object.assign(where, { blocked })
}
const query: FindOptions = {
- attributes: {
- include: [
- [
- literal(
- '(' +
- UserModel.generateUserQuotaBaseSQL({
- withSelect: false,
- whereUserId: '"UserModel"."id"'
- }) +
- ')'
- ),
- 'videoQuotaUsed'
- ]
- ]
- },
offset: start,
limit: count,
- order: getSort(sort),
+ order: getAdminUsersSort(sort),
where
}
- return UserModel.findAndCountAll(query)
- .then(({ rows, count }) => {
- return {
- data: rows,
- total: count
- }
- })
+ return Promise.all([
+ UserModel.unscoped().count(query),
+ UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query)
+ ]).then(([ total, data ]) => ({ total, data }))
}
static listWithRight (right: UserRight): Promise<MUserDefault[]> {
model: ActorFollowModel.unscoped(),
required: true,
where: {
+ state: 'accepted',
targetActorId: actorId
}
}
ScopeNames.WITH_VIDEOCHANNELS
]
- if (withStats) scopes.push(ScopeNames.WITH_STATS)
+ if (withStats) {
+ scopes.push(ScopeNames.WITH_QUOTA)
+ scopes.push(ScopeNames.WITH_STATS)
+ }
return UserModel.scope(scopes).findByPk(id)
}
const query = {
where: {
[Op.or]: [
- where(fn('lower', col('username')), '=', fn('lower', username)),
+ where(fn('lower', col('username')), fn('lower', username) as any),
{ email }
]
static generateUserQuotaBaseSQL (options: {
whereUserId: '$userId' | '"UserModel"."id"'
withSelect: boolean
- where?: string
+ daily: boolean
}) {
- const andWhere = options.where
- ? 'AND ' + options.where
+ const andWhere = options.daily === true
+ ? 'AND "video"."createdAt" > now() - interval \'24 hours\''
: ''
const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
`WHERE "account"."userId" = ${options.whereUserId} ${andWhere}`
const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
- 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
+ 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
videoChannelJoin
const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' +
- 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' +
+ 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
videoChannelJoin
return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' +
}
}
- return UserModel.count(query)
+ return UserModel.unscoped().count(query)
}
- const totalUsers = await UserModel.count()
+ const totalUsers = await UserModel.unscoped().count()
const totalDailyActiveUsers = await getActiveUsers(1)
const totalWeeklyActiveUsers = await getActiveUsers(7)
const totalMonthlyActiveUsers = await getActiveUsers(30)
.then(u => u.map(u => u.username))
}
- canGetVideo (video: MVideoWithRights) {
- const videoUserId = video.VideoChannel.Account.userId
-
- if (video.isBlacklisted()) {
- return videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
- }
-
- if (video.privacy === VideoPrivacy.PRIVATE) {
- return video.VideoChannel && videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
- }
-
- if (video.privacy === VideoPrivacy.INTERNAL) return true
-
- return false
- }
-
hasRight (right: UserRight) {
return hasUserRight(this.role, right)
}
theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME),
pendingEmail: this.pendingEmail,
+ emailPublic: this.emailPublic,
emailVerified: this.emailVerified,
nsfwPolicy: this.nsfwPolicy,
autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist,
videoLanguages: this.videoLanguages,
- role: this.role,
- roleLabel: USER_ROLE_LABELS[this.role],
+ role: {
+ id: this.role,
+ label: USER_ROLE_LABELS[this.role]
+ },
videoQuota: this.videoQuota,
videoQuotaDaily: this.videoQuotaDaily,
+
videoQuotaUsed: videoQuotaUsed !== undefined
- ? parseInt(videoQuotaUsed + '', 10)
+ ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
: undefined,
+
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
- ? parseInt(videoQuotaUsedDaily + '', 10)
+ ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
: undefined,
+
videosCount: videosCount !== undefined
- ? parseInt(videosCount + '', 10)
+ ? forceNumber(videosCount)
: undefined,
abusesCount: abusesCount
- ? parseInt(abusesCount, 10)
+ ? forceNumber(abusesCount)
: undefined,
abusesAcceptedCount: abusesAcceptedCount
- ? parseInt(abusesAcceptedCount, 10)
+ ? forceNumber(abusesAcceptedCount)
: undefined,
abusesCreatedCount: abusesCreatedCount !== undefined
- ? parseInt(abusesCreatedCount + '', 10)
+ ? forceNumber(abusesCreatedCount)
: undefined,
videoCommentsCount: videoCommentsCount !== undefined
- ? parseInt(videoCommentsCount + '', 10)
+ ? forceNumber(videoCommentsCount)
: undefined,
noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
pluginAuth: this.pluginAuth,
- lastLoginDate: this.lastLoginDate
+ lastLoginDate: this.lastLoginDate,
+
+ twoFactorEnabled: !!this.otpSecret
}
if (parameters.withAdminFlags) {