]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/user/user.ts
Implement video transcoding on server side
[github/Chocobozzz/PeerTube.git] / server / models / user / user.ts
1 import { values } from 'lodash'
2 import * as Sequelize from 'sequelize'
3 import * as Promise from 'bluebird'
4
5 import { getSort } from '../utils'
6 import { USER_ROLES } from '../../initializers'
7 import {
8 cryptPassword,
9 comparePassword,
10 isUserPasswordValid,
11 isUserUsernameValid,
12 isUserDisplayNSFWValid,
13 isUserVideoQuotaValid
14 } from '../../helpers'
15 import { VideoResolution } from '../../../shared'
16
17 import { addMethodsToModel } from '../utils'
18 import {
19 UserInstance,
20 UserAttributes,
21
22 UserMethods
23 } from './user-interface'
24
25 let User: Sequelize.Model<UserInstance, UserAttributes>
26 let isPasswordMatch: UserMethods.IsPasswordMatch
27 let toFormattedJSON: UserMethods.ToFormattedJSON
28 let isAdmin: UserMethods.IsAdmin
29 let countTotal: UserMethods.CountTotal
30 let getByUsername: UserMethods.GetByUsername
31 let list: UserMethods.List
32 let listForApi: UserMethods.ListForApi
33 let loadById: UserMethods.LoadById
34 let loadByUsername: UserMethods.LoadByUsername
35 let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
36 let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
37
38 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
39 User = sequelize.define<UserInstance, UserAttributes>('User',
40 {
41 password: {
42 type: DataTypes.STRING,
43 allowNull: false,
44 validate: {
45 passwordValid: value => {
46 const res = isUserPasswordValid(value)
47 if (res === false) throw new Error('Password not valid.')
48 }
49 }
50 },
51 username: {
52 type: DataTypes.STRING,
53 allowNull: false,
54 validate: {
55 usernameValid: value => {
56 const res = isUserUsernameValid(value)
57 if (res === false) throw new Error('Username not valid.')
58 }
59 }
60 },
61 email: {
62 type: DataTypes.STRING(400),
63 allowNull: false,
64 validate: {
65 isEmail: true
66 }
67 },
68 displayNSFW: {
69 type: DataTypes.BOOLEAN,
70 allowNull: false,
71 defaultValue: false,
72 validate: {
73 nsfwValid: value => {
74 const res = isUserDisplayNSFWValid(value)
75 if (res === false) throw new Error('Display NSFW is not valid.')
76 }
77 }
78 },
79 role: {
80 type: DataTypes.ENUM(values(USER_ROLES)),
81 allowNull: false
82 },
83 videoQuota: {
84 type: DataTypes.BIGINT,
85 allowNull: false,
86 validate: {
87 videoQuotaValid: value => {
88 const res = isUserVideoQuotaValid(value)
89 if (res === false) throw new Error('Video quota is not valid.')
90 }
91 }
92 }
93 },
94 {
95 indexes: [
96 {
97 fields: [ 'username' ],
98 unique: true
99 },
100 {
101 fields: [ 'email' ],
102 unique: true
103 }
104 ],
105 hooks: {
106 beforeCreate: beforeCreateOrUpdate,
107 beforeUpdate: beforeCreateOrUpdate
108 }
109 }
110 )
111
112 const classMethods = [
113 associate,
114
115 countTotal,
116 getByUsername,
117 list,
118 listForApi,
119 loadById,
120 loadByUsername,
121 loadByUsernameOrEmail
122 ]
123 const instanceMethods = [
124 isPasswordMatch,
125 toFormattedJSON,
126 isAdmin,
127 isAbleToUploadVideo
128 ]
129 addMethodsToModel(User, classMethods, instanceMethods)
130
131 return User
132 }
133
134 function beforeCreateOrUpdate (user: UserInstance) {
135 return cryptPassword(user.password).then(hash => {
136 user.password = hash
137 return undefined
138 })
139 }
140
141 // ------------------------------ METHODS ------------------------------
142
143 isPasswordMatch = function (this: UserInstance, password: string) {
144 return comparePassword(password, this.password)
145 }
146
147 toFormattedJSON = function (this: UserInstance) {
148 return {
149 id: this.id,
150 username: this.username,
151 email: this.email,
152 displayNSFW: this.displayNSFW,
153 role: this.role,
154 videoQuota: this.videoQuota,
155 createdAt: this.createdAt
156 }
157 }
158
159 isAdmin = function (this: UserInstance) {
160 return this.role === USER_ROLES.ADMIN
161 }
162
163 isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) {
164 if (this.videoQuota === -1) return Promise.resolve(true)
165
166 return getOriginalVideoFileTotalFromUser(this).then(totalBytes => {
167 return (videoFile.size + totalBytes) < this.videoQuota
168 })
169 }
170
171 // ------------------------------ STATICS ------------------------------
172
173 function associate (models) {
174 User.hasOne(models.Author, {
175 foreignKey: 'userId',
176 onDelete: 'cascade'
177 })
178
179 User.hasMany(models.OAuthToken, {
180 foreignKey: 'userId',
181 onDelete: 'cascade'
182 })
183 }
184
185 countTotal = function () {
186 return this.count()
187 }
188
189 getByUsername = function (username: string) {
190 const query = {
191 where: {
192 username: username
193 }
194 }
195
196 return User.findOne(query)
197 }
198
199 list = function () {
200 return User.findAll()
201 }
202
203 listForApi = function (start: number, count: number, sort: string) {
204 const query = {
205 offset: start,
206 limit: count,
207 order: [ getSort(sort) ]
208 }
209
210 return User.findAndCountAll(query).then(({ rows, count }) => {
211 return {
212 data: rows,
213 total: count
214 }
215 })
216 }
217
218 loadById = function (id: number) {
219 return User.findById(id)
220 }
221
222 loadByUsername = function (username: string) {
223 const query = {
224 where: {
225 username
226 }
227 }
228
229 return User.findOne(query)
230 }
231
232 loadByUsernameOrEmail = function (username: string, email: string) {
233 const query = {
234 where: {
235 $or: [ { username }, { email } ]
236 }
237 }
238
239 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387
240 return (User as any).findOne(query)
241 }
242
243 // ---------------------------------------------------------------------------
244
245 function getOriginalVideoFileTotalFromUser (user: UserInstance) {
246 // attributes = [] because we don't want other fields than the sum
247 const query = {
248 where: {
249 resolution: VideoResolution.ORIGINAL
250 },
251 include: [
252 {
253 attributes: [],
254 model: User['sequelize'].models.Video,
255 include: [
256 {
257 attributes: [],
258 model: User['sequelize'].models.Author,
259 include: [
260 {
261 attributes: [],
262 model: User['sequelize'].models.User,
263 where: {
264 id: user.id
265 }
266 }
267 ]
268 }
269 ]
270 }
271 ]
272 }
273
274 return User['sequelize'].models.VideoFile.sum('size', query)
275 }