]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user.ts
Add beautiful loading bar
[github/Chocobozzz/PeerTube.git] / server / models / account / user.ts
CommitLineData
e02643f3 1import * as Sequelize from 'sequelize'
e34c85e5 2import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
65fcc311 3import {
65fcc311 4 comparePassword,
e34c85e5
C
5 cryptPassword,
6 isUserDisplayNSFWValid,
65fcc311 7 isUserPasswordValid,
e34c85e5 8 isUserRoleValid,
65fcc311 9 isUserUsernameValid,
e34c85e5 10 isUserVideoQuotaValid
74889a71 11} from '../../helpers'
e34c85e5
C
12import { addMethodsToModel, getSort } from '../utils'
13import { UserAttributes, UserInstance, UserMethods } from './user-interface'
e02643f3
C
14
15let User: Sequelize.Model<UserInstance, UserAttributes>
16let isPasswordMatch: UserMethods.IsPasswordMatch
954605a8 17let hasRight: UserMethods.HasRight
0aef76c4 18let toFormattedJSON: UserMethods.ToFormattedJSON
e02643f3
C
19let countTotal: UserMethods.CountTotal
20let getByUsername: UserMethods.GetByUsername
e02643f3
C
21let listForApi: UserMethods.ListForApi
22let loadById: UserMethods.LoadById
23let loadByUsername: UserMethods.LoadByUsername
72c7248b 24let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels
e02643f3 25let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
b0f9f39e 26let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
e02643f3 27
127944aa
C
28export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
29 User = sequelize.define<UserInstance, UserAttributes>('User',
feb4bdfd
C
30 {
31 password: {
67bf9b96
C
32 type: DataTypes.STRING,
33 allowNull: false,
34 validate: {
075f16ca 35 passwordValid: value => {
65fcc311 36 const res = isUserPasswordValid(value)
67bf9b96
C
37 if (res === false) throw new Error('Password not valid.')
38 }
39 }
feb4bdfd
C
40 },
41 username: {
67bf9b96
C
42 type: DataTypes.STRING,
43 allowNull: false,
44 validate: {
075f16ca 45 usernameValid: value => {
65fcc311 46 const res = isUserUsernameValid(value)
67bf9b96
C
47 if (res === false) throw new Error('Username not valid.')
48 }
49 }
feb4bdfd 50 },
ad4a8a1c 51 email: {
5804c0db 52 type: DataTypes.STRING(400),
ad4a8a1c
C
53 allowNull: false,
54 validate: {
55 isEmail: true
56 }
57 },
1d49e1e2
C
58 displayNSFW: {
59 type: DataTypes.BOOLEAN,
60 allowNull: false,
61 defaultValue: false,
62 validate: {
075f16ca 63 nsfwValid: value => {
65fcc311 64 const res = isUserDisplayNSFWValid(value)
1d49e1e2
C
65 if (res === false) throw new Error('Display NSFW is not valid.')
66 }
67 }
68 },
feb4bdfd 69 role: {
954605a8
C
70 type: DataTypes.INTEGER,
71 allowNull: false,
72 validate: {
73 roleValid: value => {
74 const res = isUserRoleValid(value)
75 if (res === false) throw new Error('Role is not valid.')
76 }
77 }
b0f9f39e
C
78 },
79 videoQuota: {
80 type: DataTypes.BIGINT,
81 allowNull: false,
82 validate: {
83 videoQuotaValid: value => {
84 const res = isUserVideoQuotaValid(value)
85 if (res === false) throw new Error('Video quota is not valid.')
86 }
87 }
feb4bdfd
C
88 }
89 },
90 {
319d072e
C
91 indexes: [
92 {
5d67f289
C
93 fields: [ 'username' ],
94 unique: true
ad4a8a1c
C
95 },
96 {
97 fields: [ 'email' ],
98 unique: true
319d072e
C
99 }
100 ],
feb4bdfd
C
101 hooks: {
102 beforeCreate: beforeCreateOrUpdate,
103 beforeUpdate: beforeCreateOrUpdate
104 }
105 }
106 )
107
e02643f3
C
108 const classMethods = [
109 associate,
110
111 countTotal,
112 getByUsername,
e02643f3
C
113 listForApi,
114 loadById,
115 loadByUsername,
72c7248b 116 loadByUsernameAndPopulateChannels,
e02643f3
C
117 loadByUsernameOrEmail
118 ]
119 const instanceMethods = [
954605a8 120 hasRight,
e02643f3 121 isPasswordMatch,
0aef76c4 122 toFormattedJSON,
b0f9f39e 123 isAbleToUploadVideo
e02643f3
C
124 ]
125 addMethodsToModel(User, classMethods, instanceMethods)
126
feb4bdfd 127 return User
9bd26629 128}
69b0a27c 129
69818c93 130function beforeCreateOrUpdate (user: UserInstance) {
59557c46
C
131 if (user.changed('password')) {
132 return cryptPassword(user.password)
133 .then(hash => {
134 user.password = hash
135 return undefined
136 })
137 }
feb4bdfd 138}
69b0a27c 139
26d7d31b
C
140// ------------------------------ METHODS ------------------------------
141
954605a8
C
142hasRight = function (this: UserInstance, right: UserRight) {
143 return hasUserRight(this.role, right)
144}
145
6fcd19ba
C
146isPasswordMatch = function (this: UserInstance, password: string) {
147 return comparePassword(password, this.password)
26d7d31b
C
148}
149
0aef76c4 150toFormattedJSON = function (this: UserInstance) {
72c7248b 151 const json = {
feb4bdfd 152 id: this.id,
26d7d31b 153 username: this.username,
ad4a8a1c 154 email: this.email,
1d49e1e2 155 displayNSFW: this.displayNSFW,
d74a0680 156 role: this.role,
954605a8 157 roleLabel: USER_ROLE_LABELS[this.role],
b0f9f39e 158 videoQuota: this.videoQuota,
72c7248b 159 createdAt: this.createdAt,
2295ce6c 160 account: this.Account.toFormattedJSON()
26d7d31b 161 }
72c7248b 162
e4f97bab
C
163 if (Array.isArray(this.Account.VideoChannels) === true) {
164 const videoChannels = this.Account.VideoChannels
72c7248b
C
165 .map(c => c.toFormattedJSON())
166 .sort((v1, v2) => {
167 if (v1.createdAt < v2.createdAt) return -1
168 if (v1.createdAt === v2.createdAt) return 0
169
170 return 1
171 })
172
173 json['videoChannels'] = videoChannels
174 }
175
176 return json
26d7d31b 177}
198b205c 178
b0f9f39e
C
179isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) {
180 if (this.videoQuota === -1) return Promise.resolve(true)
181
182 return getOriginalVideoFileTotalFromUser(this).then(totalBytes => {
183 return (videoFile.size + totalBytes) < this.videoQuota
184 })
185}
186
26d7d31b 187// ------------------------------ STATICS ------------------------------
69b0a27c 188
feb4bdfd 189function associate (models) {
e4f97bab 190 User.hasOne(models.Account, {
4712081f
C
191 foreignKey: 'userId',
192 onDelete: 'cascade'
193 })
194
e02643f3 195 User.hasMany(models.OAuthToken, {
feb4bdfd
C
196 foreignKey: 'userId',
197 onDelete: 'cascade'
198 })
199}
200
6fcd19ba
C
201countTotal = function () {
202 return this.count()
089ff2f2
C
203}
204
69818c93 205getByUsername = function (username: string) {
feb4bdfd
C
206 const query = {
207 where: {
208 username: username
72c7248b 209 },
e4f97bab 210 include: [ { model: User['sequelize'].models.Account, required: true } ]
feb4bdfd
C
211 }
212
e02643f3 213 return User.findOne(query)
9bd26629
C
214}
215
6fcd19ba 216listForApi = function (start: number, count: number, sort: string) {
feb4bdfd
C
217 const query = {
218 offset: start,
219 limit: count,
72c7248b 220 order: [ getSort(sort) ],
e4f97bab 221 include: [ { model: User['sequelize'].models.Account, required: true } ]
feb4bdfd
C
222 }
223
6fcd19ba
C
224 return User.findAndCountAll(query).then(({ rows, count }) => {
225 return {
226 data: rows,
227 total: count
228 }
feb4bdfd 229 })
69b0a27c
C
230}
231
6fcd19ba 232loadById = function (id: number) {
72c7248b 233 const options = {
e4f97bab 234 include: [ { model: User['sequelize'].models.Account, required: true } ]
72c7248b
C
235 }
236
237 return User.findById(id, options)
68a3b9f2
C
238}
239
6fcd19ba 240loadByUsername = function (username: string) {
feb4bdfd
C
241 const query = {
242 where: {
556ddc31 243 username
72c7248b 244 },
e4f97bab 245 include: [ { model: User['sequelize'].models.Account, required: true } ]
72c7248b
C
246 }
247
248 return User.findOne(query)
249}
250
251loadByUsernameAndPopulateChannels = function (username: string) {
252 const query = {
253 where: {
254 username
255 },
256 include: [
257 {
e4f97bab 258 model: User['sequelize'].models.Account,
72c7248b
C
259 required: true,
260 include: [ User['sequelize'].models.VideoChannel ]
261 }
262 ]
feb4bdfd
C
263 }
264
6fcd19ba 265 return User.findOne(query)
9bd26629 266}
ad4a8a1c 267
6fcd19ba 268loadByUsernameOrEmail = function (username: string, email: string) {
ad4a8a1c 269 const query = {
e4f97bab 270 include: [ { model: User['sequelize'].models.Account, required: true } ],
ad4a8a1c 271 where: {
c2962505 272 [Sequelize.Op.or]: [ { username }, { email } ]
ad4a8a1c
C
273 }
274 }
275
556ddc31
C
276 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387
277 return (User as any).findOne(query)
ad4a8a1c 278}
b0f9f39e
C
279
280// ---------------------------------------------------------------------------
281
282function getOriginalVideoFileTotalFromUser (user: UserInstance) {
72c7248b 283 // Don't use sequelize because we need to use a sub query
14d3270f
C
284 const query = 'SELECT SUM("size") AS "total" FROM ' +
285 '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
286 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
72c7248b 287 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
38fa2065 288 'INNER JOIN "Accounts" ON "VideoChannels"."accountId" = "Accounts"."id" ' +
e4f97bab 289 'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' +
14d3270f
C
290 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
291
292 const options = {
293 bind: { userId: user.id },
294 type: Sequelize.QueryTypes.SELECT
b0f9f39e 295 }
14d3270f
C
296 return User['sequelize'].query(query, options).then(([ { total } ]) => {
297 if (total === null) return 0
b0f9f39e 298
14d3270f
C
299 return parseInt(total, 10)
300 })
b0f9f39e 301}