]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user.ts
Fix issues on server start
[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,
38fa2065 160 account: {
e4f97bab
C
161 id: this.Account.id,
162 uuid: this.Account.uuid
72c7248b 163 }
26d7d31b 164 }
72c7248b 165
e4f97bab
C
166 if (Array.isArray(this.Account.VideoChannels) === true) {
167 const videoChannels = this.Account.VideoChannels
72c7248b
C
168 .map(c => c.toFormattedJSON())
169 .sort((v1, v2) => {
170 if (v1.createdAt < v2.createdAt) return -1
171 if (v1.createdAt === v2.createdAt) return 0
172
173 return 1
174 })
175
176 json['videoChannels'] = videoChannels
177 }
178
179 return json
26d7d31b 180}
198b205c 181
b0f9f39e
C
182isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) {
183 if (this.videoQuota === -1) return Promise.resolve(true)
184
185 return getOriginalVideoFileTotalFromUser(this).then(totalBytes => {
186 return (videoFile.size + totalBytes) < this.videoQuota
187 })
188}
189
26d7d31b 190// ------------------------------ STATICS ------------------------------
69b0a27c 191
feb4bdfd 192function associate (models) {
e4f97bab 193 User.hasOne(models.Account, {
4712081f
C
194 foreignKey: 'userId',
195 onDelete: 'cascade'
196 })
197
e02643f3 198 User.hasMany(models.OAuthToken, {
feb4bdfd
C
199 foreignKey: 'userId',
200 onDelete: 'cascade'
201 })
202}
203
6fcd19ba
C
204countTotal = function () {
205 return this.count()
089ff2f2
C
206}
207
69818c93 208getByUsername = function (username: string) {
feb4bdfd
C
209 const query = {
210 where: {
211 username: username
72c7248b 212 },
e4f97bab 213 include: [ { model: User['sequelize'].models.Account, required: true } ]
feb4bdfd
C
214 }
215
e02643f3 216 return User.findOne(query)
9bd26629
C
217}
218
6fcd19ba 219listForApi = function (start: number, count: number, sort: string) {
feb4bdfd
C
220 const query = {
221 offset: start,
222 limit: count,
72c7248b 223 order: [ getSort(sort) ],
e4f97bab 224 include: [ { model: User['sequelize'].models.Account, required: true } ]
feb4bdfd
C
225 }
226
6fcd19ba
C
227 return User.findAndCountAll(query).then(({ rows, count }) => {
228 return {
229 data: rows,
230 total: count
231 }
feb4bdfd 232 })
69b0a27c
C
233}
234
6fcd19ba 235loadById = function (id: number) {
72c7248b 236 const options = {
e4f97bab 237 include: [ { model: User['sequelize'].models.Account, required: true } ]
72c7248b
C
238 }
239
240 return User.findById(id, options)
68a3b9f2
C
241}
242
6fcd19ba 243loadByUsername = function (username: string) {
feb4bdfd
C
244 const query = {
245 where: {
556ddc31 246 username
72c7248b 247 },
e4f97bab 248 include: [ { model: User['sequelize'].models.Account, required: true } ]
72c7248b
C
249 }
250
251 return User.findOne(query)
252}
253
254loadByUsernameAndPopulateChannels = function (username: string) {
255 const query = {
256 where: {
257 username
258 },
259 include: [
260 {
e4f97bab 261 model: User['sequelize'].models.Account,
72c7248b
C
262 required: true,
263 include: [ User['sequelize'].models.VideoChannel ]
264 }
265 ]
feb4bdfd
C
266 }
267
6fcd19ba 268 return User.findOne(query)
9bd26629 269}
ad4a8a1c 270
6fcd19ba 271loadByUsernameOrEmail = function (username: string, email: string) {
ad4a8a1c 272 const query = {
e4f97bab 273 include: [ { model: User['sequelize'].models.Account, required: true } ],
ad4a8a1c 274 where: {
c2962505 275 [Sequelize.Op.or]: [ { username }, { email } ]
ad4a8a1c
C
276 }
277 }
278
556ddc31
C
279 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387
280 return (User as any).findOne(query)
ad4a8a1c 281}
b0f9f39e
C
282
283// ---------------------------------------------------------------------------
284
285function getOriginalVideoFileTotalFromUser (user: UserInstance) {
72c7248b 286 // Don't use sequelize because we need to use a sub query
14d3270f
C
287 const query = 'SELECT SUM("size") AS "total" FROM ' +
288 '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
289 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
72c7248b 290 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
38fa2065 291 'INNER JOIN "Accounts" ON "VideoChannels"."accountId" = "Accounts"."id" ' +
e4f97bab 292 'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' +
14d3270f
C
293 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
294
295 const options = {
296 bind: { userId: user.id },
297 type: Sequelize.QueryTypes.SELECT
b0f9f39e 298 }
14d3270f
C
299 return User['sequelize'].query(query, options).then(([ { total } ]) => {
300 if (total === null) return 0
b0f9f39e 301
14d3270f
C
302 return parseInt(total, 10)
303 })
b0f9f39e 304}