]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user.ts
Use sequelize scopes
[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,
23 isUserVideoQuotaValid
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
85 @AllowNull(false)
86 @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
87 @Column
88 role: number
89
90 @AllowNull(false)
91 @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota'))
92 @Column(DataType.BIGINT)
93 videoQuota: number
94
95 @CreatedAt
96 createdAt: Date
97
98 @UpdatedAt
99 updatedAt: Date
100
101 @HasOne(() => AccountModel, {
102 foreignKey: 'userId',
103 onDelete: 'cascade'
104 })
105 Account: AccountModel
69b0a27c 106
3fd3ab2d
C
107 @HasMany(() => OAuthTokenModel, {
108 foreignKey: 'userId',
109 onDelete: 'cascade'
110 })
111 OAuthTokens: OAuthTokenModel[]
112
113 @BeforeCreate
114 @BeforeUpdate
115 static cryptPasswordIfNeeded (instance: UserModel) {
116 if (instance.changed('password')) {
117 return cryptPassword(instance.password)
118 .then(hash => {
119 instance.password = hash
120 return undefined
121 })
122 }
59557c46 123 }
26d7d31b 124
3fd3ab2d
C
125 static countTotal () {
126 return this.count()
127 }
954605a8 128
3fd3ab2d
C
129 static getByUsername (username: string) {
130 const query = {
131 where: {
132 username: username
133 },
134 include: [ { model: AccountModel, required: true } ]
135 }
26d7d31b 136
3fd3ab2d 137 return UserModel.findOne(query)
26d7d31b 138 }
72c7248b 139
3fd3ab2d
C
140 static listForApi (start: number, count: number, sort: string) {
141 const query = {
142 offset: start,
143 limit: count,
d48ff09d 144 order: [ getSort(sort) ]
3fd3ab2d 145 }
72c7248b 146
3fd3ab2d
C
147 return UserModel.findAndCountAll(query)
148 .then(({ rows, count }) => {
149 return {
150 data: rows,
151 total: count
152 }
72c7248b 153 })
72c7248b
C
154 }
155
3fd3ab2d 156 static loadById (id: number) {
d48ff09d 157 return UserModel.findById(id)
3fd3ab2d 158 }
feb4bdfd 159
3fd3ab2d
C
160 static loadByUsername (username: string) {
161 const query = {
162 where: {
163 username
d48ff09d 164 }
3fd3ab2d 165 }
089ff2f2 166
3fd3ab2d 167 return UserModel.findOne(query)
feb4bdfd
C
168 }
169
3fd3ab2d
C
170 static loadByUsernameAndPopulateChannels (username: string) {
171 const query = {
172 where: {
173 username
d48ff09d 174 }
3fd3ab2d 175 }
9bd26629 176
d48ff09d 177 return UserModel.scope('withVideoChannel').findOne(query)
feb4bdfd
C
178 }
179
3fd3ab2d
C
180 static loadByUsernameOrEmail (username: string, email: string) {
181 const query = {
3fd3ab2d
C
182 where: {
183 [ Sequelize.Op.or ]: [ { username }, { email } ]
184 }
6fcd19ba 185 }
69b0a27c 186
d48ff09d 187 return UserModel.findOne(query)
72c7248b
C
188 }
189
3fd3ab2d
C
190 private static getOriginalVideoFileTotalFromUser (user: UserModel) {
191 // Don't use sequelize because we need to use a sub query
192 const query = 'SELECT SUM("size") AS "total" FROM ' +
193 '(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
194 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
195 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
196 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
197 'INNER JOIN "user" ON "account"."userId" = "user"."id" ' +
198 'WHERE "user"."id" = $userId GROUP BY "video"."id") t'
199
200 const options = {
201 bind: { userId: user.id },
202 type: Sequelize.QueryTypes.SELECT
203 }
204 return UserModel.sequelize.query(query, options)
205 .then(([ { total } ]) => {
206 if (total === null) return 0
68a3b9f2 207
3fd3ab2d
C
208 return parseInt(total, 10)
209 })
72c7248b
C
210 }
211
3fd3ab2d
C
212 hasRight (right: UserRight) {
213 return hasUserRight(this.role, right)
214 }
72c7248b 215
3fd3ab2d
C
216 isPasswordMatch (password: string) {
217 return comparePassword(password, this.password)
feb4bdfd
C
218 }
219
3fd3ab2d
C
220 toFormattedJSON () {
221 const json = {
222 id: this.id,
223 username: this.username,
224 email: this.email,
225 displayNSFW: this.displayNSFW,
226 role: this.role,
227 roleLabel: USER_ROLE_LABELS[ this.role ],
228 videoQuota: this.videoQuota,
229 createdAt: this.createdAt,
230 account: this.Account.toFormattedJSON()
231 }
232
233 if (Array.isArray(this.Account.VideoChannels) === true) {
234 json['videoChannels'] = this.Account.VideoChannels
235 .map(c => c.toFormattedJSON())
236 .sort((v1, v2) => {
237 if (v1.createdAt < v2.createdAt) return -1
238 if (v1.createdAt === v2.createdAt) return 0
ad4a8a1c 239
3fd3ab2d
C
240 return 1
241 })
ad4a8a1c 242 }
3fd3ab2d
C
243
244 return json
ad4a8a1c
C
245 }
246
3fd3ab2d
C
247 isAbleToUploadVideo (videoFile: Express.Multer.File) {
248 if (this.videoQuota === -1) return Promise.resolve(true)
b0f9f39e 249
3fd3ab2d
C
250 return UserModel.getOriginalVideoFileTotalFromUser(this)
251 .then(totalBytes => {
252 return (videoFile.size + totalBytes) < this.videoQuota
253 })
b0f9f39e 254 }
b0f9f39e 255}