]>
Commit | Line | Data |
---|---|---|
d03cd8bb C |
1 | import * as express from 'express' |
2 | import 'multer' | |
3 | import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' | |
4 | import { getFormattedObjects } from '../../../helpers/utils' | |
5 | import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../../initializers' | |
6 | import { sendUpdateActor } from '../../../lib/activitypub/send' | |
7 | import { | |
993cef4b C |
8 | asyncMiddleware, |
9 | asyncRetryTransactionMiddleware, | |
d03cd8bb | 10 | authenticate, |
06a05d5f | 11 | commonVideosFiltersValidator, |
d03cd8bb C |
12 | paginationValidator, |
13 | setDefaultPagination, | |
14 | setDefaultSort, | |
06a05d5f | 15 | userSubscriptionAddValidator, |
99492dbc | 16 | userSubscriptionGetValidator, |
d03cd8bb C |
17 | usersUpdateMeValidator, |
18 | usersVideoRatingValidator | |
19 | } from '../../../middlewares' | |
06a05d5f | 20 | import { |
993cef4b | 21 | areSubscriptionsExistValidator, |
06a05d5f C |
22 | deleteMeValidator, |
23 | userSubscriptionsSortValidator, | |
24 | videoImportsSortValidator, | |
993cef4b | 25 | videosSortValidator |
06a05d5f | 26 | } from '../../../middlewares/validators' |
d03cd8bb C |
27 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
28 | import { UserModel } from '../../../models/account/user' | |
29 | import { VideoModel } from '../../../models/video/video' | |
30 | import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' | |
06a05d5f | 31 | import { buildNSFWFilter, createReqFiles } from '../../../helpers/express-utils' |
d03cd8bb C |
32 | import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' |
33 | import { updateAvatarValidator } from '../../../middlewares/validators/avatar' | |
34 | import { updateActorAvatarFile } from '../../../lib/avatar' | |
993cef4b | 35 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
d03cd8bb | 36 | import { VideoImportModel } from '../../../models/video/video-import' |
06a05d5f C |
37 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
38 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |
39 | import { JobQueue } from '../../../lib/job-queue' | |
40 | import { logger } from '../../../helpers/logger' | |
91411dba | 41 | import { AccountModel } from '../../../models/account/account' |
d03cd8bb C |
42 | |
43 | const auditLogger = auditLoggerFactory('users-me') | |
44 | ||
45 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) | |
46 | ||
47 | const meRouter = express.Router() | |
48 | ||
49 | meRouter.get('/me', | |
50 | authenticate, | |
51 | asyncMiddleware(getUserInformation) | |
52 | ) | |
53 | meRouter.delete('/me', | |
54 | authenticate, | |
55 | asyncMiddleware(deleteMeValidator), | |
56 | asyncMiddleware(deleteMe) | |
57 | ) | |
58 | ||
59 | meRouter.get('/me/video-quota-used', | |
60 | authenticate, | |
61 | asyncMiddleware(getUserVideoQuotaUsed) | |
62 | ) | |
63 | ||
64 | meRouter.get('/me/videos/imports', | |
65 | authenticate, | |
66 | paginationValidator, | |
67 | videoImportsSortValidator, | |
68 | setDefaultSort, | |
69 | setDefaultPagination, | |
70 | asyncMiddleware(getUserVideoImports) | |
71 | ) | |
72 | ||
73 | meRouter.get('/me/videos', | |
74 | authenticate, | |
75 | paginationValidator, | |
76 | videosSortValidator, | |
77 | setDefaultSort, | |
78 | setDefaultPagination, | |
79 | asyncMiddleware(getUserVideos) | |
80 | ) | |
81 | ||
82 | meRouter.get('/me/videos/:videoId/rating', | |
83 | authenticate, | |
84 | asyncMiddleware(usersVideoRatingValidator), | |
85 | asyncMiddleware(getUserVideoRating) | |
86 | ) | |
87 | ||
88 | meRouter.put('/me', | |
89 | authenticate, | |
a890d1e0 | 90 | asyncMiddleware(usersUpdateMeValidator), |
176e2114 | 91 | asyncRetryTransactionMiddleware(updateMe) |
d03cd8bb C |
92 | ) |
93 | ||
94 | meRouter.post('/me/avatar/pick', | |
95 | authenticate, | |
96 | reqAvatarFile, | |
97 | updateAvatarValidator, | |
176e2114 | 98 | asyncRetryTransactionMiddleware(updateMyAvatar) |
d03cd8bb C |
99 | ) |
100 | ||
06a05d5f C |
101 | // ##### Subscriptions part ##### |
102 | ||
99492dbc | 103 | meRouter.get('/me/subscriptions/videos', |
99492dbc C |
104 | authenticate, |
105 | paginationValidator, | |
106 | videosSortValidator, | |
107 | setDefaultSort, | |
108 | setDefaultPagination, | |
109 | commonVideosFiltersValidator, | |
110 | asyncMiddleware(getUserSubscriptionVideos) | |
111 | ) | |
112 | ||
f37dc0dd C |
113 | meRouter.get('/me/subscriptions/exist', |
114 | authenticate, | |
115 | areSubscriptionsExistValidator, | |
116 | asyncMiddleware(areSubscriptionsExist) | |
117 | ) | |
118 | ||
06a05d5f C |
119 | meRouter.get('/me/subscriptions', |
120 | authenticate, | |
121 | paginationValidator, | |
122 | userSubscriptionsSortValidator, | |
123 | setDefaultSort, | |
124 | setDefaultPagination, | |
125 | asyncMiddleware(getUserSubscriptions) | |
126 | ) | |
127 | ||
128 | meRouter.post('/me/subscriptions', | |
129 | authenticate, | |
130 | userSubscriptionAddValidator, | |
131 | asyncMiddleware(addUserSubscription) | |
132 | ) | |
133 | ||
99492dbc | 134 | meRouter.get('/me/subscriptions/:uri', |
06a05d5f | 135 | authenticate, |
99492dbc C |
136 | userSubscriptionGetValidator, |
137 | getUserSubscription | |
06a05d5f C |
138 | ) |
139 | ||
99492dbc | 140 | meRouter.delete('/me/subscriptions/:uri', |
06a05d5f | 141 | authenticate, |
99492dbc | 142 | userSubscriptionGetValidator, |
176e2114 | 143 | asyncRetryTransactionMiddleware(deleteUserSubscription) |
06a05d5f C |
144 | ) |
145 | ||
d03cd8bb C |
146 | // --------------------------------------------------------------------------- |
147 | ||
148 | export { | |
149 | meRouter | |
150 | } | |
151 | ||
152 | // --------------------------------------------------------------------------- | |
153 | ||
f37dc0dd C |
154 | async function areSubscriptionsExist (req: express.Request, res: express.Response) { |
155 | const uris = req.query.uris as string[] | |
156 | const user = res.locals.oauth.token.User as UserModel | |
157 | ||
158 | const handles = uris.map(u => { | |
159 | let [ name, host ] = u.split('@') | |
160 | if (host === CONFIG.WEBSERVER.HOST) host = null | |
161 | ||
162 | return { name, host, uri: u } | |
163 | }) | |
164 | ||
165 | const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles) | |
166 | ||
167 | const existObject: { [id: string ]: boolean } = {} | |
168 | for (const handle of handles) { | |
169 | const obj = results.find(r => { | |
170 | const server = r.ActorFollowing.Server | |
171 | ||
172 | return r.ActorFollowing.preferredUsername === handle.name && | |
173 | ( | |
174 | (!server && !handle.host) || | |
175 | (server.host === handle.host) | |
176 | ) | |
177 | }) | |
178 | ||
179 | existObject[handle.uri] = obj !== undefined | |
180 | } | |
181 | ||
182 | return res.json(existObject) | |
183 | } | |
184 | ||
06a05d5f C |
185 | async function addUserSubscription (req: express.Request, res: express.Response) { |
186 | const user = res.locals.oauth.token.User as UserModel | |
187 | const [ name, host ] = req.body.uri.split('@') | |
188 | ||
189 | const payload = { | |
190 | name, | |
191 | host, | |
192 | followerActorId: user.Account.Actor.id | |
193 | } | |
194 | ||
195 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | |
196 | .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err)) | |
197 | ||
198 | return res.status(204).end() | |
199 | } | |
200 | ||
99492dbc C |
201 | function getUserSubscription (req: express.Request, res: express.Response) { |
202 | const subscription: ActorFollowModel = res.locals.subscription | |
203 | ||
204 | return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON()) | |
205 | } | |
206 | ||
06a05d5f C |
207 | async function deleteUserSubscription (req: express.Request, res: express.Response) { |
208 | const subscription: ActorFollowModel = res.locals.subscription | |
209 | ||
210 | await sequelizeTypescript.transaction(async t => { | |
211 | return subscription.destroy({ transaction: t }) | |
212 | }) | |
213 | ||
214 | return res.type('json').status(204).end() | |
215 | } | |
216 | ||
217 | async function getUserSubscriptions (req: express.Request, res: express.Response) { | |
218 | const user = res.locals.oauth.token.User as UserModel | |
219 | const actorId = user.Account.Actor.id | |
220 | ||
221 | const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort) | |
222 | ||
223 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | |
224 | } | |
225 | ||
226 | async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | |
227 | const user = res.locals.oauth.token.User as UserModel | |
228 | const resultList = await VideoModel.listForApi({ | |
229 | start: req.query.start, | |
230 | count: req.query.count, | |
231 | sort: req.query.sort, | |
232 | includeLocalVideos: false, | |
233 | categoryOneOf: req.query.categoryOneOf, | |
234 | licenceOneOf: req.query.licenceOneOf, | |
235 | languageOneOf: req.query.languageOneOf, | |
236 | tagsOneOf: req.query.tagsOneOf, | |
237 | tagsAllOf: req.query.tagsAllOf, | |
238 | nsfw: buildNSFWFilter(res, req.query.nsfw), | |
239 | filter: req.query.filter as VideoFilter, | |
240 | withFiles: false, | |
7ad9b984 C |
241 | actorId: user.Account.Actor.id, |
242 | user | |
06a05d5f C |
243 | }) |
244 | ||
245 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | |
246 | } | |
247 | ||
d03cd8bb C |
248 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
249 | const user = res.locals.oauth.token.User as UserModel | |
250 | const resultList = await VideoModel.listUserVideosForApi( | |
251 | user.Account.id, | |
252 | req.query.start as number, | |
253 | req.query.count as number, | |
254 | req.query.sort as VideoSortField | |
255 | ) | |
256 | ||
257 | const additionalAttributes = { | |
258 | waitTranscoding: true, | |
259 | state: true, | |
260 | scheduledUpdate: true, | |
261 | blacklistInfo: true | |
262 | } | |
263 | return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) | |
264 | } | |
265 | ||
266 | async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) { | |
267 | const user = res.locals.oauth.token.User as UserModel | |
268 | const resultList = await VideoImportModel.listUserVideoImportsForApi( | |
269 | user.id, | |
270 | req.query.start as number, | |
271 | req.query.count as number, | |
272 | req.query.sort | |
273 | ) | |
274 | ||
275 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | |
276 | } | |
277 | ||
278 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | |
279 | // We did not load channels in res.locals.user | |
280 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | |
281 | ||
282 | return res.json(user.toFormattedJSON()) | |
283 | } | |
284 | ||
285 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response, next: express.NextFunction) { | |
286 | // We did not load channels in res.locals.user | |
287 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | |
288 | const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) | |
bee0abff | 289 | const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user) |
d03cd8bb C |
290 | |
291 | const data: UserVideoQuota = { | |
bee0abff FA |
292 | videoQuotaUsed, |
293 | videoQuotaUsedDaily | |
d03cd8bb C |
294 | } |
295 | return res.json(data) | |
296 | } | |
297 | ||
298 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | |
627621c1 | 299 | const videoId = res.locals.video.id |
d03cd8bb C |
300 | const accountId = +res.locals.oauth.token.User.Account.id |
301 | ||
302 | const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) | |
303 | const rating = ratingObj ? ratingObj.type : 'none' | |
304 | ||
305 | const json: FormattedUserVideoRate = { | |
306 | videoId, | |
307 | rating | |
308 | } | |
06a05d5f | 309 | return res.json(json) |
d03cd8bb C |
310 | } |
311 | ||
312 | async function deleteMe (req: express.Request, res: express.Response) { | |
313 | const user: UserModel = res.locals.oauth.token.User | |
314 | ||
315 | await user.destroy() | |
316 | ||
993cef4b | 317 | auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) |
d03cd8bb C |
318 | |
319 | return res.sendStatus(204) | |
320 | } | |
321 | ||
322 | async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { | |
323 | const body: UserUpdateMe = req.body | |
324 | ||
325 | const user: UserModel = res.locals.oauth.token.user | |
326 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | |
327 | ||
328 | if (body.password !== undefined) user.password = body.password | |
329 | if (body.email !== undefined) user.email = body.email | |
330 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy | |
ed638e53 | 331 | if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled |
d03cd8bb C |
332 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo |
333 | ||
334 | await sequelizeTypescript.transaction(async t => { | |
91411dba C |
335 | const userAccount = await AccountModel.load(user.Account.id) |
336 | ||
d03cd8bb C |
337 | await user.save({ transaction: t }) |
338 | ||
91411dba C |
339 | if (body.displayName !== undefined) userAccount.name = body.displayName |
340 | if (body.description !== undefined) userAccount.description = body.description | |
341 | await userAccount.save({ transaction: t }) | |
d03cd8bb | 342 | |
91411dba | 343 | await sendUpdateActor(userAccount, t) |
d03cd8bb | 344 | |
91411dba | 345 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) |
d03cd8bb C |
346 | }) |
347 | ||
348 | return res.sendStatus(204) | |
349 | } | |
350 | ||
351 | async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { | |
352 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | |
353 | const user: UserModel = res.locals.oauth.token.user | |
354 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | |
d03cd8bb | 355 | |
91411dba | 356 | const userAccount = await AccountModel.load(user.Account.id) |
d03cd8bb | 357 | |
f201a749 | 358 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) |
91411dba C |
359 | |
360 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) | |
d03cd8bb | 361 | |
06a05d5f | 362 | return res.json({ avatar: avatar.toFormattedJSON() }) |
d03cd8bb | 363 | } |