diff options
Diffstat (limited to 'server/controllers/api/videos')
-rw-r--r-- | server/controllers/api/videos/abuse.ts | 8 | ||||
-rw-r--r-- | server/controllers/api/videos/comment.ts | 10 | ||||
-rw-r--r-- | server/controllers/api/videos/import.ts | 138 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 17 |
4 files changed, 168 insertions, 5 deletions
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 { | |||
18 | import { AccountModel } from '../../../models/account/account' | 18 | import { AccountModel } from '../../../models/account/account' |
19 | import { VideoModel } from '../../../models/video/video' | 19 | import { VideoModel } from '../../../models/video/video' |
20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | ||
21 | 22 | ||
23 | const auditLogger = auditLoggerFactory('abuse') | ||
22 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
23 | 25 | ||
24 | abuseVideoRouter.get('/abuse', | 26 | abuseVideoRouter.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' |
24 | import { VideoModel } from '../../../models/video/video' | 24 | import { VideoModel } from '../../../models/video/video' |
25 | import { VideoCommentModel } from '../../../models/video/video-comment' | 25 | import { VideoCommentModel } from '../../../models/video/video-comment' |
26 | import { auditLoggerFactory, CommentAuditView } from '../../../helpers/audit-logger' | ||
26 | 27 | ||
28 | const auditLogger = auditLoggerFactory('comments') | ||
27 | const videoCommentRouter = express.Router() | 29 | const videoCommentRouter = express.Router() |
28 | 30 | ||
29 | videoCommentRouter.get('/:videoId/comment-threads', | 31 | videoCommentRouter.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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger' | ||
3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | ||
4 | import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' | ||
5 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | ||
6 | import { createReqFiles } from '../../../helpers/express-utils' | ||
7 | import { logger } from '../../../helpers/logger' | ||
8 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | ||
9 | import { VideoModel } from '../../../models/video/video' | ||
10 | import { getVideoActivityPubUrl } from '../../../lib/activitypub' | ||
11 | import { TagModel } from '../../../models/video/tag' | ||
12 | import { VideoImportModel } from '../../../models/video/video-import' | ||
13 | import { JobQueue } from '../../../lib/job-queue/job-queue' | ||
14 | import { processImage } from '../../../helpers/image-utils' | ||
15 | import { join } from 'path' | ||
16 | |||
17 | const auditLogger = auditLoggerFactory('video-imports') | ||
18 | const videoImportsRouter = express.Router() | ||
19 | |||
20 | const 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 | |||
29 | videoImportsRouter.post('/imports', | ||
30 | authenticate, | ||
31 | reqVideoFileImport, | ||
32 | asyncMiddleware(videoImportAddValidator), | ||
33 | asyncRetryTransactionMiddleware(addVideoImport) | ||
34 | ) | ||
35 | |||
36 | // --------------------------------------------------------------------------- | ||
37 | |||
38 | export { | ||
39 | videoImportsRouter | ||
40 | } | ||
41 | |||
42 | // --------------------------------------------------------------------------- | ||
43 | |||
44 | async 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' | |||
5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
6 | import { processImage } from '../../../helpers/image-utils' | 6 | import { processImage } from '../../../helpers/image-utils' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { auditLoggerFactory, VideoAuditView } from '../../../helpers/audit-logger' | ||
8 | import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' | 9 | import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' |
9 | import { | 10 | import { |
10 | CONFIG, | 11 | CONFIG, |
@@ -53,7 +54,9 @@ import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | |||
53 | import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' | 54 | import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' |
54 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 55 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
55 | import { videoCaptionsRouter } from './captions' | 56 | import { videoCaptionsRouter } from './captions' |
57 | import { videoImportsRouter } from './import' | ||
56 | 58 | ||
59 | const auditLogger = auditLoggerFactory('videos') | ||
57 | const videosRouter = express.Router() | 60 | const videosRouter = express.Router() |
58 | 61 | ||
59 | const reqVideoFileAdd = createReqFiles( | 62 | const reqVideoFileAdd = createReqFiles( |
@@ -79,6 +82,7 @@ videosRouter.use('/', blacklistRouter) | |||
79 | videosRouter.use('/', rateVideoRouter) | 82 | videosRouter.use('/', rateVideoRouter) |
80 | videosRouter.use('/', videoCommentRouter) | 83 | videosRouter.use('/', videoCommentRouter) |
81 | videosRouter.use('/', videoCaptionsRouter) | 84 | videosRouter.use('/', videoCaptionsRouter) |
85 | videosRouter.use('/', videoImportsRouter) | ||
82 | 86 | ||
83 | videosRouter.get('/categories', listVideoCategories) | 87 | videosRouter.get('/categories', listVideoCategories) |
84 | videosRouter.get('/licences', listVideoLicences) | 88 | videosRouter.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) { | |||
273 | async function updateVideo (req: express.Request, res: express.Response) { | 277 | async 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() |