Automatically resize avatars
[oweals/peertube.git] / server / controllers / api / users.ts
1 import * as express from 'express'
2 import { extname, join } from 'path'
3 import * as sharp from 'sharp'
4 import * as uuidv4 from 'uuid/v4'
5 import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
6 import { renamePromise, unlinkPromise } from '../../helpers/core-utils'
7 import { retryTransactionWrapper } from '../../helpers/database-utils'
8 import { logger } from '../../helpers/logger'
9 import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
10 import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
11 import { createUserAccountAndChannel } from '../../lib/user'
12 import {
13   asyncMiddleware, authenticate, ensureUserHasRight, ensureUserRegistrationAllowed, paginationValidator, setPagination, setUsersSort,
14   setVideosSort, token, usersAddValidator, usersGetValidator, usersRegisterValidator, usersRemoveValidator, usersSortValidator,
15   usersUpdateMeValidator, usersUpdateValidator, usersVideoRatingValidator
16 } from '../../middlewares'
17 import { usersUpdateMyAvatarValidator, videosSortValidator } from '../../middlewares/validators'
18 import { AccountVideoRateModel } from '../../models/account/account-video-rate'
19 import { UserModel } from '../../models/account/user'
20 import { AvatarModel } from '../../models/avatar/avatar'
21 import { VideoModel } from '../../models/video/video'
22
23 const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT)
24
25 const usersRouter = express.Router()
26
27 usersRouter.get('/me',
28   authenticate,
29   asyncMiddleware(getUserInformation)
30 )
31
32 usersRouter.get('/me/videos',
33   authenticate,
34   paginationValidator,
35   videosSortValidator,
36   setVideosSort,
37   setPagination,
38   asyncMiddleware(getUserVideos)
39 )
40
41 usersRouter.get('/me/videos/:videoId/rating',
42   authenticate,
43   asyncMiddleware(usersVideoRatingValidator),
44   asyncMiddleware(getUserVideoRating)
45 )
46
47 usersRouter.get('/',
48   authenticate,
49   ensureUserHasRight(UserRight.MANAGE_USERS),
50   paginationValidator,
51   usersSortValidator,
52   setUsersSort,
53   setPagination,
54   asyncMiddleware(listUsers)
55 )
56
57 usersRouter.get('/:id',
58   asyncMiddleware(usersGetValidator),
59   getUser
60 )
61
62 usersRouter.post('/',
63   authenticate,
64   ensureUserHasRight(UserRight.MANAGE_USERS),
65   asyncMiddleware(usersAddValidator),
66   asyncMiddleware(createUserRetryWrapper)
67 )
68
69 usersRouter.post('/register',
70   asyncMiddleware(ensureUserRegistrationAllowed),
71   asyncMiddleware(usersRegisterValidator),
72   asyncMiddleware(registerUserRetryWrapper)
73 )
74
75 usersRouter.put('/me',
76   authenticate,
77   usersUpdateMeValidator,
78   asyncMiddleware(updateMe)
79 )
80
81 usersRouter.post('/me/avatar/pick',
82   authenticate,
83   reqAvatarFile,
84   usersUpdateMyAvatarValidator,
85   asyncMiddleware(updateMyAvatar)
86 )
87
88 usersRouter.put('/:id',
89   authenticate,
90   ensureUserHasRight(UserRight.MANAGE_USERS),
91   asyncMiddleware(usersUpdateValidator),
92   asyncMiddleware(updateUser)
93 )
94
95 usersRouter.delete('/:id',
96   authenticate,
97   ensureUserHasRight(UserRight.MANAGE_USERS),
98   asyncMiddleware(usersRemoveValidator),
99   asyncMiddleware(removeUser)
100 )
101
102 usersRouter.post('/token', token, success)
103 // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
104
105 // ---------------------------------------------------------------------------
106
107 export {
108   usersRouter
109 }
110
111 // ---------------------------------------------------------------------------
112
113 async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
114   const user = res.locals.oauth.token.User as UserModel
115   const resultList = await VideoModel.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort)
116
117   return res.json(getFormattedObjects(resultList.data, resultList.total))
118 }
119
120 async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
121   const options = {
122     arguments: [ req ],
123     errorMessage: 'Cannot insert the user with many retries.'
124   }
125
126   await retryTransactionWrapper(createUser, options)
127
128   // TODO : include Location of the new user -> 201
129   return res.type('json').status(204).end()
130 }
131
132 async function createUser (req: express.Request) {
133   const body: UserCreate = req.body
134   const user = new UserModel({
135     username: body.username,
136     password: body.password,
137     email: body.email,
138     displayNSFW: false,
139     autoPlayVideo: true,
140     role: body.role,
141     videoQuota: body.videoQuota
142   })
143
144   await createUserAccountAndChannel(user)
145
146   logger.info('User %s with its channel and account created.', body.username)
147 }
148
149 async function registerUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
150   const options = {
151     arguments: [ req ],
152     errorMessage: 'Cannot insert the user with many retries.'
153   }
154
155   await retryTransactionWrapper(registerUser, options)
156
157   return res.type('json').status(204).end()
158 }
159
160 async function registerUser (req: express.Request) {
161   const body: UserCreate = req.body
162
163   const user = new UserModel({
164     username: body.username,
165     password: body.password,
166     email: body.email,
167     displayNSFW: false,
168     autoPlayVideo: true,
169     role: UserRole.USER,
170     videoQuota: CONFIG.USER.VIDEO_QUOTA
171   })
172
173   await createUserAccountAndChannel(user)
174
175   logger.info('User %s with its channel and account registered.', body.username)
176 }
177
178 async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
179   // We did not load channels in res.locals.user
180   const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
181
182   return res.json(user.toFormattedJSON())
183 }
184
185 function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
186   return res.json(res.locals.user.toFormattedJSON())
187 }
188
189 async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
190   const videoId = +req.params.videoId
191   const accountId = +res.locals.oauth.token.User.Account.id
192
193   const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
194   const rating = ratingObj ? ratingObj.type : 'none'
195
196   const json: FormattedUserVideoRate = {
197     videoId,
198     rating
199   }
200   res.json(json)
201 }
202
203 async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
204   const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
205
206   return res.json(getFormattedObjects(resultList.data, resultList.total))
207 }
208
209 async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
210   const user = await UserModel.loadById(req.params.id)
211
212   await user.destroy()
213
214   return res.sendStatus(204)
215 }
216
217 async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
218   const body: UserUpdateMe = req.body
219
220   // FIXME: user is not already a Sequelize instance?
221   const user = res.locals.oauth.token.user
222
223   if (body.password !== undefined) user.password = body.password
224   if (body.email !== undefined) user.email = body.email
225   if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
226   if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
227
228   await user.save()
229
230   return res.sendStatus(204)
231 }
232
233 async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
234   const avatarPhysicalFile = req.files['avatarfile'][0]
235   const actor = res.locals.oauth.token.user.Account.Actor
236
237   const avatarDir = CONFIG.STORAGE.AVATARS_DIR
238   const source = join(avatarDir, avatarPhysicalFile.filename)
239   const extension = extname(avatarPhysicalFile.filename)
240   const avatarName = uuidv4() + extension
241   const destination = join(avatarDir, avatarName)
242
243   await sharp(source)
244     .resize(AVATARS_SIZE.width, AVATARS_SIZE.height)
245     .toFile(destination)
246
247   await unlinkPromise(source)
248
249   const { avatar } = await sequelizeTypescript.transaction(async t => {
250     const avatar = await AvatarModel.create({
251       filename: avatarName
252     }, { transaction: t })
253
254     if (actor.Avatar) {
255       await actor.Avatar.destroy({ transaction: t })
256     }
257
258     actor.set('avatarId', avatar.id)
259     await actor.save({ transaction: t })
260
261     return { actor, avatar }
262   })
263
264   return res
265     .json({
266       avatar: avatar.toFormattedJSON()
267     })
268     .end()
269 }
270
271 async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
272   const body: UserUpdate = req.body
273   const user = res.locals.user as UserModel
274
275   if (body.email !== undefined) user.email = body.email
276   if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
277   if (body.role !== undefined) user.role = body.role
278
279   await user.save()
280
281   return res.sendStatus(204)
282 }
283
284 function success (req: express.Request, res: express.Response, next: express.NextFunction) {
285   res.end()
286 }