]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/account/user.ts
70ed61e0783dfe9fa75d9556385b0e23e60c5963
[github/Chocobozzz/PeerTube.git] / server / models / account / user.ts
1 import * as Sequelize from 'sequelize'
2 import {
3 AllowNull,
4 BeforeCreate,
5 BeforeUpdate,
6 Column, CreatedAt,
7 DataType,
8 Default, DefaultScope,
9 HasMany,
10 HasOne,
11 Is,
12 IsEmail,
13 Model, Scopes,
14 Table, UpdatedAt
15 } from 'sequelize-typescript'
16 import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
17 import {
18 comparePassword,
19 cryptPassword
20 } from '../../helpers'
21 import {
22 isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
23 isUserVideoQuotaValid, isUserAutoPlayVideoValid
24 } from '../../helpers/custom-validators/users'
25 import { OAuthTokenModel } from '../oauth/oauth-token'
26 import { getSort, throwIfNotValid } from '../utils'
27 import { VideoChannelModel } from '../video/video-channel'
28 import { AccountModel } from './account'
29
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 })
49 @Table({
50 tableName: 'user',
51 indexes: [
52 {
53 fields: [ 'username' ],
54 unique: true
55 },
56 {
57 fields: [ 'email' ],
58 unique: true
59 }
60 ]
61 })
62 export 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
85 @AllowNull(false)
86 @Default(true)
87 @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
88 @Column
89 autoPlayVideo: boolean
90
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
112
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 }
129 }
130
131 static countTotal () {
132 return this.count()
133 }
134
135 static getByUsername (username: string) {
136 const query = {
137 where: {
138 username: username
139 },
140 include: [ { model: AccountModel, required: true } ]
141 }
142
143 return UserModel.findOne(query)
144 }
145
146 static listForApi (start: number, count: number, sort: string) {
147 const query = {
148 offset: start,
149 limit: count,
150 order: [ getSort(sort) ]
151 }
152
153 return UserModel.findAndCountAll(query)
154 .then(({ rows, count }) => {
155 return {
156 data: rows,
157 total: count
158 }
159 })
160 }
161
162 static loadById (id: number) {
163 return UserModel.findById(id)
164 }
165
166 static loadByUsername (username: string) {
167 const query = {
168 where: {
169 username
170 }
171 }
172
173 return UserModel.findOne(query)
174 }
175
176 static loadByUsernameAndPopulateChannels (username: string) {
177 const query = {
178 where: {
179 username
180 }
181 }
182
183 return UserModel.scope('withVideoChannel').findOne(query)
184 }
185
186 static loadByUsernameOrEmail (username: string, email: string) {
187 const query = {
188 where: {
189 [ Sequelize.Op.or ]: [ { username }, { email } ]
190 }
191 }
192
193 return UserModel.findOne(query)
194 }
195
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
213
214 return parseInt(total, 10)
215 })
216 }
217
218 hasRight (right: UserRight) {
219 return hasUserRight(this.role, right)
220 }
221
222 isPasswordMatch (password: string) {
223 return comparePassword(password, this.password)
224 }
225
226 toFormattedJSON () {
227 const json = {
228 id: this.id,
229 username: this.username,
230 email: this.email,
231 displayNSFW: this.displayNSFW,
232 autoPlayVideo: this.autoPlayVideo,
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
246
247 return 1
248 })
249 }
250
251 return json
252 }
253
254 isAbleToUploadVideo (videoFile: Express.Multer.File) {
255 if (this.videoQuota === -1) return Promise.resolve(true)
256
257 return UserModel.getOriginalVideoFileTotalFromUser(this)
258 .then(totalBytes => {
259 return (videoFile.size + totalBytes) < this.videoQuota
260 })
261 }
262 }