aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/api/config.ts30
-rw-r--r--server/controllers/api/users.ts78
-rw-r--r--server/controllers/api/video-channel.ts29
-rw-r--r--server/controllers/api/videos/abuse.ts8
-rw-r--r--server/controllers/api/videos/comment.ts10
-rw-r--r--server/controllers/api/videos/import.ts138
-rw-r--r--server/controllers/api/videos/index.ts17
7 files changed, 291 insertions, 19 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 9c1b2818c..950a1498e 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -9,10 +9,13 @@ import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
9import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' 9import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
10import { customConfigUpdateValidator } from '../../middlewares/validators/config' 10import { customConfigUpdateValidator } from '../../middlewares/validators/config'
11import { ClientHtml } from '../../lib/client-html' 11import { ClientHtml } from '../../lib/client-html'
12import { CustomConfigAuditView, auditLoggerFactory } from '../../helpers/audit-logger'
12 13
13const packageJSON = require('../../../../package.json') 14const packageJSON = require('../../../../package.json')
14const configRouter = express.Router() 15const configRouter = express.Router()
15 16
17const auditLogger = auditLoggerFactory('config')
18
16configRouter.get('/about', getAbout) 19configRouter.get('/about', getAbout)
17configRouter.get('/', 20configRouter.get('/',
18 asyncMiddleware(getConfig) 21 asyncMiddleware(getConfig)
@@ -62,6 +65,13 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
62 transcoding: { 65 transcoding: {
63 enabledResolutions 66 enabledResolutions
64 }, 67 },
68 import: {
69 videos: {
70 http: {
71 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
72 }
73 }
74 },
65 avatar: { 75 avatar: {
66 file: { 76 file: {
67 size: { 77 size: {
@@ -119,6 +129,11 @@ async function getCustomConfig (req: express.Request, res: express.Response, nex
119async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 129async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
120 await unlinkPromise(CONFIG.CUSTOM_FILE) 130 await unlinkPromise(CONFIG.CUSTOM_FILE)
121 131
132 auditLogger.delete(
133 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
134 new CustomConfigAuditView(customConfig())
135 )
136
122 reloadConfig() 137 reloadConfig()
123 ClientHtml.invalidCache() 138 ClientHtml.invalidCache()
124 139
@@ -129,6 +144,7 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
129 144
130async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 145async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
131 const toUpdate: CustomConfig = req.body 146 const toUpdate: CustomConfig = req.body
147 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
132 148
133 // Force number conversion 149 // Force number conversion
134 toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) 150 toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10)
@@ -150,6 +166,13 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
150 ClientHtml.invalidCache() 166 ClientHtml.invalidCache()
151 167
152 const data = customConfig() 168 const data = customConfig()
169
170 auditLogger.update(
171 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
172 new CustomConfigAuditView(data),
173 oldCustomConfigAuditKeys
174 )
175
153 return res.json(data).end() 176 return res.json(data).end()
154} 177}
155 178
@@ -209,6 +232,13 @@ function customConfig (): CustomConfig {
209 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 232 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
210 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] 233 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
211 } 234 }
235 },
236 import: {
237 videos: {
238 http: {
239 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
240 }
241 }
212 } 242 }
213 } 243 }
214} 244}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index c80f27a23..879ba3f91 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -29,7 +29,12 @@ import {
29 usersUpdateValidator, 29 usersUpdateValidator,
30 usersVideoRatingValidator 30 usersVideoRatingValidator
31} from '../../middlewares' 31} from '../../middlewares'
32import { usersAskResetPasswordValidator, usersResetPasswordValidator, videosSortValidator } from '../../middlewares/validators' 32import {
33 usersAskResetPasswordValidator,
34 usersResetPasswordValidator,
35 videoImportsSortValidator,
36 videosSortValidator
37} from '../../middlewares/validators'
33import { AccountVideoRateModel } from '../../models/account/account-video-rate' 38import { AccountVideoRateModel } from '../../models/account/account-video-rate'
34import { UserModel } from '../../models/account/user' 39import { UserModel } from '../../models/account/user'
35import { OAuthTokenModel } from '../../models/oauth/oauth-token' 40import { OAuthTokenModel } from '../../models/oauth/oauth-token'
@@ -39,6 +44,10 @@ import { createReqFiles } from '../../helpers/express-utils'
39import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model' 44import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model'
40import { updateAvatarValidator } from '../../middlewares/validators/avatar' 45import { updateAvatarValidator } from '../../middlewares/validators/avatar'
41import { updateActorAvatarFile } from '../../lib/avatar' 46import { updateActorAvatarFile } from '../../lib/avatar'
47import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger'
48import { VideoImportModel } from '../../models/video/video-import'
49
50const auditLogger = auditLoggerFactory('users')
42 51
43const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 52const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
44const loginRateLimiter = new RateLimit({ 53const loginRateLimiter = new RateLimit({
@@ -59,6 +68,15 @@ usersRouter.get('/me/video-quota-used',
59 asyncMiddleware(getUserVideoQuotaUsed) 68 asyncMiddleware(getUserVideoQuotaUsed)
60) 69)
61 70
71usersRouter.get('/me/videos/imports',
72 authenticate,
73 paginationValidator,
74 videoImportsSortValidator,
75 setDefaultSort,
76 setDefaultPagination,
77 asyncMiddleware(getUserVideoImports)
78)
79
62usersRouter.get('/me/videos', 80usersRouter.get('/me/videos',
63 authenticate, 81 authenticate,
64 paginationValidator, 82 paginationValidator,
@@ -175,6 +193,18 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
175 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 193 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
176} 194}
177 195
196async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) {
197 const user = res.locals.oauth.token.User as UserModel
198 const resultList = await VideoImportModel.listUserVideoImportsForApi(
199 user.Account.id,
200 req.query.start as number,
201 req.query.count as number,
202 req.query.sort
203 )
204
205 return res.json(getFormattedObjects(resultList.data, resultList.total))
206}
207
178async function createUser (req: express.Request, res: express.Response) { 208async function createUser (req: express.Request, res: express.Response) {
179 const body: UserCreate = req.body 209 const body: UserCreate = req.body
180 const userToCreate = new UserModel({ 210 const userToCreate = new UserModel({
@@ -189,6 +219,7 @@ async function createUser (req: express.Request, res: express.Response) {
189 219
190 const { user, account } = await createUserAccountAndChannel(userToCreate) 220 const { user, account } = await createUserAccountAndChannel(userToCreate)
191 221
222 auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON()))
192 logger.info('User %s with its channel and account created.', body.username) 223 logger.info('User %s with its channel and account created.', body.username)
193 224
194 return res.json({ 225 return res.json({
@@ -205,7 +236,7 @@ async function createUser (req: express.Request, res: express.Response) {
205async function registerUser (req: express.Request, res: express.Response) { 236async function registerUser (req: express.Request, res: express.Response) {
206 const body: UserCreate = req.body 237 const body: UserCreate = req.body
207 238
208 const user = new UserModel({ 239 const userToCreate = new UserModel({
209 username: body.username, 240 username: body.username,
210 password: body.password, 241 password: body.password,
211 email: body.email, 242 email: body.email,
@@ -215,8 +246,9 @@ async function registerUser (req: express.Request, res: express.Response) {
215 videoQuota: CONFIG.USER.VIDEO_QUOTA 246 videoQuota: CONFIG.USER.VIDEO_QUOTA
216 }) 247 })
217 248
218 await createUserAccountAndChannel(user) 249 const { user } = await createUserAccountAndChannel(userToCreate)
219 250
251 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
220 logger.info('User %s with its channel and account registered.', body.username) 252 logger.info('User %s with its channel and account registered.', body.username)
221 253
222 return res.type('json').status(204).end() 254 return res.type('json').status(204).end()
@@ -269,6 +301,8 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
269 301
270 await user.destroy() 302 await user.destroy()
271 303
304 auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON()))
305
272 return res.sendStatus(204) 306 return res.sendStatus(204)
273} 307}
274 308
@@ -276,6 +310,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
276 const body: UserUpdateMe = req.body 310 const body: UserUpdateMe = req.body
277 311
278 const user: UserModel = res.locals.oauth.token.user 312 const user: UserModel = res.locals.oauth.token.user
313 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
279 314
280 if (body.password !== undefined) user.password = body.password 315 if (body.password !== undefined) user.password = body.password
281 if (body.email !== undefined) user.email = body.email 316 if (body.email !== undefined) user.email = body.email
@@ -290,6 +325,12 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
290 await user.Account.save({ transaction: t }) 325 await user.Account.save({ transaction: t })
291 326
292 await sendUpdateActor(user.Account, t) 327 await sendUpdateActor(user.Account, t)
328
329 auditLogger.update(
330 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
331 new UserAuditView(user.toFormattedJSON()),
332 oldUserAuditView
333 )
293 }) 334 })
294 335
295 return res.sendStatus(204) 336 return res.sendStatus(204)
@@ -297,10 +338,18 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
297 338
298async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { 339async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
299 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 340 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
300 const account = res.locals.oauth.token.user.Account 341 const user: UserModel = res.locals.oauth.token.user
342 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
343 const account = user.Account
301 344
302 const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account) 345 const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account)
303 346
347 auditLogger.update(
348 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
349 new UserAuditView(user.toFormattedJSON()),
350 oldUserAuditView
351 )
352
304 return res 353 return res
305 .json({ 354 .json({
306 avatar: avatar.toFormattedJSON() 355 avatar: avatar.toFormattedJSON()
@@ -310,20 +359,27 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
310 359
311async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 360async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
312 const body: UserUpdate = req.body 361 const body: UserUpdate = req.body
313 const user = res.locals.user as UserModel 362 const userToUpdate = res.locals.user as UserModel
314 const roleChanged = body.role !== undefined && body.role !== user.role 363 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
364 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
315 365
316 if (body.email !== undefined) user.email = body.email 366 if (body.email !== undefined) userToUpdate.email = body.email
317 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota 367 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
318 if (body.role !== undefined) user.role = body.role 368 if (body.role !== undefined) userToUpdate.role = body.role
319 369
320 await user.save() 370 const user = await userToUpdate.save()
321 371
322 // Destroy user token to refresh rights 372 // Destroy user token to refresh rights
323 if (roleChanged) { 373 if (roleChanged) {
324 await OAuthTokenModel.deleteUserToken(user.id) 374 await OAuthTokenModel.deleteUserToken(userToUpdate.id)
325 } 375 }
326 376
377 auditLogger.update(
378 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
379 new UserAuditView(user.toFormattedJSON()),
380 oldUserAuditView
381 )
382
327 // Don't need to send this update to followers, these attributes are not propagated 383 // Don't need to send this update to followers, these attributes are not propagated
328 384
329 return res.sendStatus(204) 385 return res.sendStatus(204)
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 0488ba8f5..3a444547b 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -27,7 +27,9 @@ import { logger } from '../../helpers/logger'
27import { VideoModel } from '../../models/video/video' 27import { VideoModel } from '../../models/video/video'
28import { updateAvatarValidator } from '../../middlewares/validators/avatar' 28import { updateAvatarValidator } from '../../middlewares/validators/avatar'
29import { updateActorAvatarFile } from '../../lib/avatar' 29import { updateActorAvatarFile } from '../../lib/avatar'
30import { auditLoggerFactory, VideoChannelAuditView } from '../../helpers/audit-logger'
30 31
32const auditLogger = auditLoggerFactory('channels')
31const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 33const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
32 34
33const videoChannelRouter = express.Router() 35const videoChannelRouter = express.Router()
@@ -99,10 +101,17 @@ async function listVideoChannels (req: express.Request, res: express.Response, n
99 101
100async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { 102async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
101 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 103 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
102 const videoChannel = res.locals.videoChannel 104 const videoChannel = res.locals.videoChannel as VideoChannelModel
105 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
103 106
104 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel) 107 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel)
105 108
109 auditLogger.update(
110 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
111 new VideoChannelAuditView(videoChannel.toFormattedJSON()),
112 oldVideoChannelAuditKeys
113 )
114
106 return res 115 return res
107 .json({ 116 .json({
108 avatar: avatar.toFormattedJSON() 117 avatar: avatar.toFormattedJSON()
@@ -121,6 +130,10 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
121 setAsyncActorKeys(videoChannelCreated.Actor) 130 setAsyncActorKeys(videoChannelCreated.Actor)
122 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err })) 131 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
123 132
133 auditLogger.create(
134 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
135 new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())
136 )
124 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) 137 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
125 138
126 return res.json({ 139 return res.json({
@@ -134,6 +147,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
134async function updateVideoChannel (req: express.Request, res: express.Response) { 147async function updateVideoChannel (req: express.Request, res: express.Response) {
135 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel 148 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
136 const videoChannelFieldsSave = videoChannelInstance.toJSON() 149 const videoChannelFieldsSave = videoChannelInstance.toJSON()
150 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
137 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate 151 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
138 152
139 try { 153 try {
@@ -148,9 +162,14 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
148 162
149 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) 163 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
150 await sendUpdateActor(videoChannelInstanceUpdated, t) 164 await sendUpdateActor(videoChannelInstanceUpdated, t)
151 })
152 165
153 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) 166 auditLogger.update(
167 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
168 new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
169 oldVideoChannelAuditKeys
170 )
171 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
172 })
154 } catch (err) { 173 } catch (err) {
155 logger.debug('Cannot update the video channel.', { err }) 174 logger.debug('Cannot update the video channel.', { err })
156 175
@@ -171,6 +190,10 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
171 await sequelizeTypescript.transaction(async t => { 190 await sequelizeTypescript.transaction(async t => {
172 await videoChannelInstance.destroy({ transaction: t }) 191 await videoChannelInstance.destroy({ transaction: t })
173 192
193 auditLogger.delete(
194 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
195 new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
196 )
174 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) 197 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
175 }) 198 })
176 199
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 3413ae894..7782fc639 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -18,7 +18,9 @@ import {
18import { AccountModel } from '../../../models/account/account' 18import { AccountModel } from '../../../models/account/account'
19import { VideoModel } from '../../../models/video/video' 19import { VideoModel } from '../../../models/video/video'
20import { VideoAbuseModel } from '../../../models/video/video-abuse' 20import { VideoAbuseModel } from '../../../models/video/video-abuse'
21import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
21 22
23const auditLogger = auditLoggerFactory('abuse')
22const abuseVideoRouter = express.Router() 24const abuseVideoRouter = express.Router()
23 25
24abuseVideoRouter.get('/abuse', 26abuseVideoRouter.get('/abuse',
@@ -64,14 +66,16 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
64 await sequelizeTypescript.transaction(async t => { 66 await sequelizeTypescript.transaction(async t => {
65 const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) 67 const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
66 videoAbuseInstance.Video = videoInstance 68 videoAbuseInstance.Video = videoInstance
69 videoAbuseInstance.Account = reporterAccount
67 70
68 // We send the video abuse to the origin server 71 // We send the video abuse to the origin server
69 if (videoInstance.isOwned() === false) { 72 if (videoInstance.isOwned() === false) {
70 await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) 73 await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
71 } 74 }
72 })
73 75
74 logger.info('Abuse report for video %s created.', videoInstance.name) 76 auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
77 logger.info('Abuse report for video %s created.', videoInstance.name)
78 })
75 79
76 return res.type('json').status(204).end() 80 return res.type('json').status(204).end()
77} 81}
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index bbeb0d557..e35247829 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -23,7 +23,9 @@ import {
23} from '../../../middlewares/validators/video-comments' 23} from '../../../middlewares/validators/video-comments'
24import { VideoModel } from '../../../models/video/video' 24import { VideoModel } from '../../../models/video/video'
25import { VideoCommentModel } from '../../../models/video/video-comment' 25import { VideoCommentModel } from '../../../models/video/video-comment'
26import { auditLoggerFactory, CommentAuditView } from '../../../helpers/audit-logger'
26 27
28const auditLogger = auditLoggerFactory('comments')
27const videoCommentRouter = express.Router() 29const videoCommentRouter = express.Router()
28 30
29videoCommentRouter.get('/:videoId/comment-threads', 31videoCommentRouter.get('/:videoId/comment-threads',
@@ -107,6 +109,8 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
107 }, t) 109 }, t)
108 }) 110 })
109 111
112 auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON()))
113
110 return res.json({ 114 return res.json({
111 comment: comment.toFormattedJSON() 115 comment: comment.toFormattedJSON()
112 }).end() 116 }).end()
@@ -124,6 +128,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
124 }, t) 128 }, t)
125 }) 129 })
126 130
131 auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON()))
132
127 return res.json({ 133 return res.json({
128 comment: comment.toFormattedJSON() 134 comment: comment.toFormattedJSON()
129 }).end() 135 }).end()
@@ -136,6 +142,10 @@ async function removeVideoComment (req: express.Request, res: express.Response)
136 await videoCommentInstance.destroy({ transaction: t }) 142 await videoCommentInstance.destroy({ transaction: t })
137 }) 143 })
138 144
145 auditLogger.delete(
146 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
147 new CommentAuditView(videoCommentInstance.toFormattedJSON())
148 )
139 logger.info('Video comment %d deleted.', videoCommentInstance.id) 149 logger.info('Video comment %d deleted.', videoCommentInstance.id)
140 150
141 return res.type('json').status(204).end() 151 return res.type('json').status(204).end()
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
new file mode 100644
index 000000000..30a7d816c
--- /dev/null
+++ b/server/controllers/api/videos/import.ts
@@ -0,0 +1,138 @@
1import * as express from 'express'
2import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger'
3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
4import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
5import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
6import { createReqFiles } from '../../../helpers/express-utils'
7import { logger } from '../../../helpers/logger'
8import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
9import { VideoModel } from '../../../models/video/video'
10import { getVideoActivityPubUrl } from '../../../lib/activitypub'
11import { TagModel } from '../../../models/video/tag'
12import { VideoImportModel } from '../../../models/video/video-import'
13import { JobQueue } from '../../../lib/job-queue/job-queue'
14import { processImage } from '../../../helpers/image-utils'
15import { join } from 'path'
16
17const auditLogger = auditLoggerFactory('video-imports')
18const videoImportsRouter = express.Router()
19
20const reqVideoFileImport = createReqFiles(
21 [ 'thumbnailfile', 'previewfile' ],
22 IMAGE_MIMETYPE_EXT,
23 {
24 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
25 previewfile: CONFIG.STORAGE.PREVIEWS_DIR
26 }
27)
28
29videoImportsRouter.post('/imports',
30 authenticate,
31 reqVideoFileImport,
32 asyncMiddleware(videoImportAddValidator),
33 asyncRetryTransactionMiddleware(addVideoImport)
34)
35
36// ---------------------------------------------------------------------------
37
38export {
39 videoImportsRouter
40}
41
42// ---------------------------------------------------------------------------
43
44async function addVideoImport (req: express.Request, res: express.Response) {
45 const body: VideoImportCreate = req.body
46 const targetUrl = body.targetUrl
47
48 let youtubeDLInfo: YoutubeDLInfo
49 try {
50 youtubeDLInfo = await getYoutubeDLInfo(targetUrl)
51 } catch (err) {
52 logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err })
53
54 return res.status(400).json({
55 error: 'Cannot fetch remote information of this URL.'
56 }).end()
57 }
58
59 // Create video DB object
60 const videoData = {
61 name: body.name || youtubeDLInfo.name,
62 remote: false,
63 category: body.category || youtubeDLInfo.category,
64 licence: body.licence || youtubeDLInfo.licence,
65 language: body.language || undefined,
66 commentsEnabled: body.commentsEnabled || true,
67 waitTranscoding: body.waitTranscoding || false,
68 state: VideoState.TO_IMPORT,
69 nsfw: body.nsfw || youtubeDLInfo.nsfw || false,
70 description: body.description || youtubeDLInfo.description,
71 support: body.support || null,
72 privacy: body.privacy || VideoPrivacy.PRIVATE,
73 duration: 0, // duration will be set by the import job
74 channelId: res.locals.videoChannel.id
75 }
76 const video = new VideoModel(videoData)
77 video.url = getVideoActivityPubUrl(video)
78
79 // Process thumbnail file?
80 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
81 let downloadThumbnail = true
82 if (thumbnailField) {
83 const thumbnailPhysicalFile = thumbnailField[ 0 ]
84 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
85 downloadThumbnail = false
86 }
87
88 // Process preview file?
89 const previewField = req.files ? req.files['previewfile'] : undefined
90 let downloadPreview = true
91 if (previewField) {
92 const previewPhysicalFile = previewField[0]
93 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
94 downloadPreview = false
95 }
96
97 const videoImport: VideoImportModel = await sequelizeTypescript.transaction(async t => {
98 const sequelizeOptions = { transaction: t }
99
100 // Save video object in database
101 const videoCreated = await video.save(sequelizeOptions)
102 videoCreated.VideoChannel = res.locals.videoChannel
103
104 // Set tags to the video
105 const tags = body.tags ? body.tags : youtubeDLInfo.tags
106 if (tags !== undefined) {
107 const tagInstances = await TagModel.findOrCreateTags(tags, t)
108
109 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
110 videoCreated.Tags = tagInstances
111 }
112
113 // Create video import object in database
114 const videoImport = await VideoImportModel.create({
115 targetUrl,
116 state: VideoImportState.PENDING,
117 videoId: videoCreated.id
118 }, sequelizeOptions)
119
120 videoImport.Video = videoCreated
121
122 return videoImport
123 })
124
125 // Create job to import the video
126 const payload = {
127 type: 'youtube-dl' as 'youtube-dl',
128 videoImportId: videoImport.id,
129 thumbnailUrl: youtubeDLInfo.thumbnailUrl,
130 downloadThumbnail,
131 downloadPreview
132 }
133 await JobQueue.Instance.createJob({ type: 'video-import', payload })
134
135 auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoImportAuditView(videoImport.toFormattedJSON()))
136
137 return res.json(videoImport.toFormattedJSON()).end()
138}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 101183eab..c9365da08 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -5,6 +5,7 @@ import { renamePromise } from '../../../helpers/core-utils'
5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
6import { processImage } from '../../../helpers/image-utils' 6import { processImage } from '../../../helpers/image-utils'
7import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { auditLoggerFactory, VideoAuditView } from '../../../helpers/audit-logger'
8import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' 9import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
9import { 10import {
10 CONFIG, 11 CONFIG,
@@ -53,7 +54,9 @@ import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
53import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' 54import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils'
54import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 55import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
55import { videoCaptionsRouter } from './captions' 56import { videoCaptionsRouter } from './captions'
57import { videoImportsRouter } from './import'
56 58
59const auditLogger = auditLoggerFactory('videos')
57const videosRouter = express.Router() 60const videosRouter = express.Router()
58 61
59const reqVideoFileAdd = createReqFiles( 62const reqVideoFileAdd = createReqFiles(
@@ -79,6 +82,7 @@ videosRouter.use('/', blacklistRouter)
79videosRouter.use('/', rateVideoRouter) 82videosRouter.use('/', rateVideoRouter)
80videosRouter.use('/', videoCommentRouter) 83videosRouter.use('/', videoCommentRouter)
81videosRouter.use('/', videoCaptionsRouter) 84videosRouter.use('/', videoCaptionsRouter)
85videosRouter.use('/', videoImportsRouter)
82 86
83videosRouter.get('/categories', listVideoCategories) 87videosRouter.get('/categories', listVideoCategories)
84videosRouter.get('/licences', listVideoLicences) 88videosRouter.get('/licences', listVideoLicences)
@@ -158,7 +162,6 @@ async function addVideo (req: express.Request, res: express.Response) {
158 const videoData = { 162 const videoData = {
159 name: videoInfo.name, 163 name: videoInfo.name,
160 remote: false, 164 remote: false,
161 extname: extname(videoPhysicalFile.filename),
162 category: videoInfo.category, 165 category: videoInfo.category,
163 licence: videoInfo.licence, 166 licence: videoInfo.licence,
164 language: videoInfo.language, 167 language: videoInfo.language,
@@ -247,6 +250,7 @@ async function addVideo (req: express.Request, res: express.Response) {
247 250
248 await federateVideoIfNeeded(video, true, t) 251 await federateVideoIfNeeded(video, true, t)
249 252
253 auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
250 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) 254 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
251 255
252 return videoCreated 256 return videoCreated
@@ -273,6 +277,7 @@ async function addVideo (req: express.Request, res: express.Response) {
273async function updateVideo (req: express.Request, res: express.Response) { 277async function updateVideo (req: express.Request, res: express.Response) {
274 const videoInstance: VideoModel = res.locals.video 278 const videoInstance: VideoModel = res.locals.video
275 const videoFieldsSave = videoInstance.toJSON() 279 const videoFieldsSave = videoInstance.toJSON()
280 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
276 const videoInfoToUpdate: VideoUpdate = req.body 281 const videoInfoToUpdate: VideoUpdate = req.body
277 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 282 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
278 283
@@ -344,9 +349,14 @@ async function updateVideo (req: express.Request, res: express.Response) {
344 349
345 const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE 350 const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE
346 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) 351 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
347 })
348 352
349 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) 353 auditLogger.update(
354 res.locals.oauth.token.User.Account.Actor.getIdentifier(),
355 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
356 oldVideoAuditView
357 )
358 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
359 })
350 } catch (err) { 360 } catch (err) {
351 // Force fields we want to update 361 // Force fields we want to update
352 // If the transaction is retried, sequelize will think the object has not changed 362 // If the transaction is retried, sequelize will think the object has not changed
@@ -423,6 +433,7 @@ async function removeVideo (req: express.Request, res: express.Response) {
423 await videoInstance.destroy({ transaction: t }) 433 await videoInstance.destroy({ transaction: t })
424 }) 434 })
425 435
436 auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoInstance.toFormattedDetailsJSON()))
426 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid) 437 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
427 438
428 return res.type('json').status(204).end() 439 return res.type('json').status(204).end()