]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user.ts
Save
[github/Chocobozzz/PeerTube.git] / server / models / account / user.ts
CommitLineData
e02643f3 1import * as Sequelize from 'sequelize'
3fd3ab2d
C
2import {
3 AllowNull,
4 BeforeCreate,
5 BeforeUpdate,
6 Column, CreatedAt,
7 DataType,
d48ff09d 8 Default, DefaultScope,
3fd3ab2d
C
9 HasMany,
10 HasOne,
11 Is,
12 IsEmail,
d48ff09d 13 Model, Scopes,
3fd3ab2d
C
14 Table, UpdatedAt
15} from 'sequelize-typescript'
e34c85e5 16import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
65fcc311 17import {
65fcc311 18 comparePassword,
3fd3ab2d 19 cryptPassword
74889a71 20} from '../../helpers'
3fd3ab2d
C
21import {
22 isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
7efe153b 23 isUserVideoQuotaValid, isUserAutoPlayVideoValid
3fd3ab2d
C
24} from '../../helpers/custom-validators/users'
25import { OAuthTokenModel } from '../oauth/oauth-token'
26import { getSort, throwIfNotValid } from '../utils'
27import { VideoChannelModel } from '../video/video-channel'
28import { AccountModel } from './account'
29
d48ff09d
C
30@DefaultScope({
31 include: [
32 {
33 model: () => AccountModel,
34 required: true
35 }
36 ]
37})
38@Scopes({
39 withVideoChannel: {
40 include: [
41 {
42 model: () => AccountModel,
43 required: true,
44 include: [ () => VideoChannelModel ]
45 }
46 ]
47 }
48})
3fd3ab2d
C
49@Table({
50 tableName: 'user',
51 indexes: [
feb4bdfd 52 {
3fd3ab2d
C
53 fields: [ 'username' ],
54 unique: true
feb4bdfd
C
55 },
56 {
3fd3ab2d
C
57 fields: [ 'email' ],
58 unique: true
feb4bdfd 59 }
e02643f3 60 ]
3fd3ab2d
C
61})
62export class UserModel extends Model<UserModel> {
63
64 @AllowNull(false)
65 @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password'))
66 @Column
67 password: string
68
69 @AllowNull(false)
70 @Is('UserPassword', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
71 @Column
72 username: string
73
74 @AllowNull(false)
75 @IsEmail
76 @Column(DataType.STRING(400))
77 email: string
78
79 @AllowNull(false)
80 @Default(false)
81 @Is('UserDisplayNSFW', value => throwIfNotValid(value, isUserDisplayNSFWValid, 'display NSFW boolean'))
82 @Column
83 displayNSFW: boolean
84
7efe153b
AL
85 @AllowNull(false)
86 @Default(true)
87 @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
88 @Column
89 autoPlayVideo: boolean
90
3fd3ab2d
C
91 @AllowNull(false)
92 @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
93 @Column
94 role: number
95
96 @AllowNull(false)
97 @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota'))
98 @Column(DataType.BIGINT)
99 videoQuota: number
100
101 @CreatedAt
102 createdAt: Date
103
104 @UpdatedAt
105 updatedAt: Date
106
107 @HasOne(() => AccountModel, {
108 foreignKey: 'userId',
109 onDelete: 'cascade'
110 })
111 Account: AccountModel
69b0a27c 112
3fd3ab2d
C
113 @HasMany(() => OAuthTokenModel, {
114 foreignKey: 'userId',
115 onDelete: 'cascade'
116 })
117 OAuthTokens: OAuthTokenModel[]
118
119 @BeforeCreate
120 @BeforeUpdate
121 static cryptPasswordIfNeeded (instance: UserModel) {
122 if (instance.changed('password')) {
123 return cryptPassword(instance.password)
124 .then(hash => {
125 instance.password = hash
126 return undefined
127 })
128 }
59557c46 129 }
26d7d31b 130
3fd3ab2d
C
131 static countTotal () {
132 return this.count()
133 }
954605a8 134
3fd3ab2d
C
135 static getByUsername (username: string) {
136 const query = {
137 where: {
138 username: username
139 },
140 include: [ { model: AccountModel, required: true } ]
141 }
26d7d31b 142
3fd3ab2d 143 return UserModel.findOne(query)
26d7d31b 144 }
72c7248b 145
3fd3ab2d
C
146 static listForApi (start: number, count: number, sort: string) {
147 const query = {
148 offset: start,
149 limit: count,
d48ff09d 150 order: [ getSort(sort) ]
3fd3ab2d 151 }
72c7248b 152
3fd3ab2d
C
153 return UserModel.findAndCountAll(query)
154 .then(({ rows, count }) => {
155 return {
156 data: rows,
157 total: count
158 }
72c7248b 159 })
72c7248b
C
160 }
161
3fd3ab2d 162 static loadById (id: number) {
d48ff09d 163 return UserModel.findById(id)
3fd3ab2d 164 }
feb4bdfd 165
3fd3ab2d
C
166 static loadByUsername (username: string) {
167 const query = {
168 where: {
169 username
d48ff09d 170 }
3fd3ab2d 171 }
089ff2f2 172
3fd3ab2d 173 return UserModel.findOne(query)
feb4bdfd
C
174 }
175
3fd3ab2d
C
176 static loadByUsernameAndPopulateChannels (username: string) {
177 const query = {
178 where: {
179 username
d48ff09d 180 }
3fd3ab2d 181 }
9bd26629 182
d48ff09d 183 return UserModel.scope('withVideoChannel').findOne(query)
feb4bdfd
C
184 }
185
3fd3ab2d
C
186 static loadByUsernameOrEmail (username: string, email: string) {
187 const query = {
3fd3ab2d
C
188 where: {
189 [ Sequelize.Op.or ]: [ { username }, { email } ]
190 }
6fcd19ba 191 }
69b0a27c 192
d48ff09d 193 return UserModel.findOne(query)
72c7248b
C
194 }
195
3fd3ab2d
C
196 private static getOriginalVideoFileTotalFromUser (user: UserModel) {
197 // Don't use sequelize because we need to use a sub query
198 const query = 'SELECT SUM("size") AS "total" FROM ' +
199 '(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
200 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
201 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
202 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
203 'INNER JOIN "user" ON "account"."userId" = "user"."id" ' +
204 'WHERE "user"."id" = $userId GROUP BY "video"."id") t'
205
206 const options = {
207 bind: { userId: user.id },
208 type: Sequelize.QueryTypes.SELECT
209 }
210 return UserModel.sequelize.query(query, options)
211 .then(([ { total } ]) => {
212 if (total === null) return 0
68a3b9f2 213
3fd3ab2d
C
214 return parseInt(total, 10)
215 })
72c7248b
C
216 }
217
3fd3ab2d
C
218 hasRight (right: UserRight) {
219 return hasUserRight(this.role, right)
220 }
72c7248b 221
3fd3ab2d
C
222 isPasswordMatch (password: string) {
223 return comparePassword(password, this.password)
feb4bdfd
C
224 }
225
3fd3ab2d
C
226 toFormattedJSON () {
227 const json = {
228 id: this.id,
229 username: this.username,
230 email: this.email,
231 displayNSFW: this.displayNSFW,
7efe153b 232 autoPlayVideo: this.autoPlayVideo,
3fd3ab2d
C
233 role: this.role,
234 roleLabel: USER_ROLE_LABELS[ this.role ],
235 videoQuota: this.videoQuota,
236 createdAt: this.createdAt,
237 account: this.Account.toFormattedJSON()
238 }
239
240 if (Array.isArray(this.Account.VideoChannels) === true) {
241 json['videoChannels'] = this.Account.VideoChannels
242 .map(c => c.toFormattedJSON())
243 .sort((v1, v2) => {
244 if (v1.createdAt < v2.createdAt) return -1
245 if (v1.createdAt === v2.createdAt) return 0
ad4a8a1c 246
3fd3ab2d
C
247 return 1
248 })
ad4a8a1c 249 }
3fd3ab2d
C
250
251 return json
ad4a8a1c
C
252 }
253
3fd3ab2d
C
254 isAbleToUploadVideo (videoFile: Express.Multer.File) {
255 if (this.videoQuota === -1) return Promise.resolve(true)
b0f9f39e 256
3fd3ab2d
C
257 return UserModel.getOriginalVideoFileTotalFromUser(this)
258 .then(totalBytes => {
259 return (videoFile.size + totalBytes) < this.videoQuota
260 })
b0f9f39e 261 }
b0f9f39e 262}