diff options
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/api/server/debug.ts | 18 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 108 | ||||
-rw-r--r-- | server/controllers/feeds.ts | 11 |
3 files changed, 113 insertions, 24 deletions
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts index 7787186be..ff0d9ca3c 100644 --- a/server/controllers/api/server/debug.ts +++ b/server/controllers/api/server/debug.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import { InboxManager } from '@server/lib/activitypub/inbox-manager' | 1 | import { InboxManager } from '@server/lib/activitypub/inbox-manager' |
2 | import { RemoveDanglingResumableUploadsScheduler } from '@server/lib/schedulers/remove-dangling-resumable-uploads-scheduler' | ||
3 | import { SendDebugCommand } from '@shared/models' | ||
2 | import * as express from 'express' | 4 | import * as express from 'express' |
3 | import { UserRight } from '../../../../shared/models/users' | 5 | import { UserRight } from '../../../../shared/models/users' |
4 | import { authenticate, ensureUserHasRight } from '../../../middlewares' | 6 | import { authenticate, ensureUserHasRight } from '../../../middlewares' |
@@ -11,6 +13,12 @@ debugRouter.get('/debug', | |||
11 | getDebug | 13 | getDebug |
12 | ) | 14 | ) |
13 | 15 | ||
16 | debugRouter.post('/debug/run-command', | ||
17 | authenticate, | ||
18 | ensureUserHasRight(UserRight.MANAGE_DEBUG), | ||
19 | runCommand | ||
20 | ) | ||
21 | |||
14 | // --------------------------------------------------------------------------- | 22 | // --------------------------------------------------------------------------- |
15 | 23 | ||
16 | export { | 24 | export { |
@@ -25,3 +33,13 @@ function getDebug (req: express.Request, res: express.Response) { | |||
25 | activityPubMessagesWaiting: InboxManager.Instance.getActivityPubMessagesWaiting() | 33 | activityPubMessagesWaiting: InboxManager.Instance.getActivityPubMessagesWaiting() |
26 | }) | 34 | }) |
27 | } | 35 | } |
36 | |||
37 | async function runCommand (req: express.Request, res: express.Response) { | ||
38 | const body: SendDebugCommand = req.body | ||
39 | |||
40 | if (body.command === 'remove-dandling-resumable-uploads') { | ||
41 | await RemoveDanglingResumableUploadsScheduler.Instance.execute() | ||
42 | } | ||
43 | |||
44 | return res.sendStatus(204) | ||
45 | } | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 6ec6478e4..c32626d30 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -2,6 +2,7 @@ import * as express from 'express' | |||
2 | import { move } from 'fs-extra' | 2 | import { move } from 'fs-extra' |
3 | import { extname } from 'path' | 3 | import { extname } from 'path' |
4 | import toInt from 'validator/lib/toInt' | 4 | import toInt from 'validator/lib/toInt' |
5 | import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload' | ||
5 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 6 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
6 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | 7 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' |
7 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' | 8 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' |
@@ -10,8 +11,9 @@ import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnail | |||
10 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 11 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
11 | import { getServerActor } from '@server/models/application/application' | 12 | import { getServerActor } from '@server/models/application/application' |
12 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | 13 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
14 | import { uploadx } from '@uploadx/core' | ||
13 | import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared' | 15 | import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared' |
14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 16 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs' |
15 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 17 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
16 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' | 18 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' |
17 | import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' | 19 | import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' |
@@ -47,7 +49,9 @@ import { | |||
47 | setDefaultPagination, | 49 | setDefaultPagination, |
48 | setDefaultVideosSort, | 50 | setDefaultVideosSort, |
49 | videoFileMetadataGetValidator, | 51 | videoFileMetadataGetValidator, |
50 | videosAddValidator, | 52 | videosAddLegacyValidator, |
53 | videosAddResumableInitValidator, | ||
54 | videosAddResumableValidator, | ||
51 | videosCustomGetValidator, | 55 | videosCustomGetValidator, |
52 | videosGetValidator, | 56 | videosGetValidator, |
53 | videosRemoveValidator, | 57 | videosRemoveValidator, |
@@ -69,6 +73,7 @@ import { watchingRouter } from './watching' | |||
69 | const lTags = loggerTagsFactory('api', 'video') | 73 | const lTags = loggerTagsFactory('api', 'video') |
70 | const auditLogger = auditLoggerFactory('videos') | 74 | const auditLogger = auditLoggerFactory('videos') |
71 | const videosRouter = express.Router() | 75 | const videosRouter = express.Router() |
76 | const uploadxMiddleware = uploadx.upload({ directory: getResumableUploadPath() }) | ||
72 | 77 | ||
73 | const reqVideoFileAdd = createReqFiles( | 78 | const reqVideoFileAdd = createReqFiles( |
74 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | 79 | [ 'videofile', 'thumbnailfile', 'previewfile' ], |
@@ -79,6 +84,16 @@ const reqVideoFileAdd = createReqFiles( | |||
79 | previewfile: CONFIG.STORAGE.TMP_DIR | 84 | previewfile: CONFIG.STORAGE.TMP_DIR |
80 | } | 85 | } |
81 | ) | 86 | ) |
87 | |||
88 | const reqVideoFileAddResumable = createReqFiles( | ||
89 | [ 'thumbnailfile', 'previewfile' ], | ||
90 | MIMETYPES.IMAGE.MIMETYPE_EXT, | ||
91 | { | ||
92 | thumbnailfile: getResumableUploadPath(), | ||
93 | previewfile: getResumableUploadPath() | ||
94 | } | ||
95 | ) | ||
96 | |||
82 | const reqVideoFileUpdate = createReqFiles( | 97 | const reqVideoFileUpdate = createReqFiles( |
83 | [ 'thumbnailfile', 'previewfile' ], | 98 | [ 'thumbnailfile', 'previewfile' ], |
84 | MIMETYPES.IMAGE.MIMETYPE_EXT, | 99 | MIMETYPES.IMAGE.MIMETYPE_EXT, |
@@ -111,18 +126,39 @@ videosRouter.get('/', | |||
111 | commonVideosFiltersValidator, | 126 | commonVideosFiltersValidator, |
112 | asyncMiddleware(listVideos) | 127 | asyncMiddleware(listVideos) |
113 | ) | 128 | ) |
129 | |||
130 | videosRouter.post('/upload', | ||
131 | authenticate, | ||
132 | reqVideoFileAdd, | ||
133 | asyncMiddleware(videosAddLegacyValidator), | ||
134 | asyncRetryTransactionMiddleware(addVideoLegacy) | ||
135 | ) | ||
136 | |||
137 | videosRouter.post('/upload-resumable', | ||
138 | authenticate, | ||
139 | reqVideoFileAddResumable, | ||
140 | asyncMiddleware(videosAddResumableInitValidator), | ||
141 | uploadxMiddleware | ||
142 | ) | ||
143 | |||
144 | videosRouter.delete('/upload-resumable', | ||
145 | authenticate, | ||
146 | uploadxMiddleware | ||
147 | ) | ||
148 | |||
149 | videosRouter.put('/upload-resumable', | ||
150 | authenticate, | ||
151 | uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes | ||
152 | asyncMiddleware(videosAddResumableValidator), | ||
153 | asyncMiddleware(addVideoResumable) | ||
154 | ) | ||
155 | |||
114 | videosRouter.put('/:id', | 156 | videosRouter.put('/:id', |
115 | authenticate, | 157 | authenticate, |
116 | reqVideoFileUpdate, | 158 | reqVideoFileUpdate, |
117 | asyncMiddleware(videosUpdateValidator), | 159 | asyncMiddleware(videosUpdateValidator), |
118 | asyncRetryTransactionMiddleware(updateVideo) | 160 | asyncRetryTransactionMiddleware(updateVideo) |
119 | ) | 161 | ) |
120 | videosRouter.post('/upload', | ||
121 | authenticate, | ||
122 | reqVideoFileAdd, | ||
123 | asyncMiddleware(videosAddValidator), | ||
124 | asyncRetryTransactionMiddleware(addVideo) | ||
125 | ) | ||
126 | 162 | ||
127 | videosRouter.get('/:id/description', | 163 | videosRouter.get('/:id/description', |
128 | asyncMiddleware(videosGetValidator), | 164 | asyncMiddleware(videosGetValidator), |
@@ -157,23 +193,23 @@ export { | |||
157 | 193 | ||
158 | // --------------------------------------------------------------------------- | 194 | // --------------------------------------------------------------------------- |
159 | 195 | ||
160 | function listVideoCategories (req: express.Request, res: express.Response) { | 196 | function listVideoCategories (_req: express.Request, res: express.Response) { |
161 | res.json(VIDEO_CATEGORIES) | 197 | res.json(VIDEO_CATEGORIES) |
162 | } | 198 | } |
163 | 199 | ||
164 | function listVideoLicences (req: express.Request, res: express.Response) { | 200 | function listVideoLicences (_req: express.Request, res: express.Response) { |
165 | res.json(VIDEO_LICENCES) | 201 | res.json(VIDEO_LICENCES) |
166 | } | 202 | } |
167 | 203 | ||
168 | function listVideoLanguages (req: express.Request, res: express.Response) { | 204 | function listVideoLanguages (_req: express.Request, res: express.Response) { |
169 | res.json(VIDEO_LANGUAGES) | 205 | res.json(VIDEO_LANGUAGES) |
170 | } | 206 | } |
171 | 207 | ||
172 | function listVideoPrivacies (req: express.Request, res: express.Response) { | 208 | function listVideoPrivacies (_req: express.Request, res: express.Response) { |
173 | res.json(VIDEO_PRIVACIES) | 209 | res.json(VIDEO_PRIVACIES) |
174 | } | 210 | } |
175 | 211 | ||
176 | async function addVideo (req: express.Request, res: express.Response) { | 212 | async function addVideoLegacy (req: express.Request, res: express.Response) { |
177 | // Uploading the video could be long | 213 | // Uploading the video could be long |
178 | // Set timeout to 10 minutes, as Express's default is 2 minutes | 214 | // Set timeout to 10 minutes, as Express's default is 2 minutes |
179 | req.setTimeout(1000 * 60 * 10, () => { | 215 | req.setTimeout(1000 * 60 * 10, () => { |
@@ -183,13 +219,42 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
183 | 219 | ||
184 | const videoPhysicalFile = req.files['videofile'][0] | 220 | const videoPhysicalFile = req.files['videofile'][0] |
185 | const videoInfo: VideoCreate = req.body | 221 | const videoInfo: VideoCreate = req.body |
222 | const files = req.files | ||
223 | |||
224 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
225 | } | ||
186 | 226 | ||
187 | const videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id) | 227 | async function addVideoResumable (_req: express.Request, res: express.Response) { |
188 | videoData.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED | 228 | const videoPhysicalFile = res.locals.videoFileResumable |
189 | videoData.duration = videoPhysicalFile['duration'] // duration was added by a previous middleware | 229 | const videoInfo = videoPhysicalFile.metadata |
230 | const files = { previewfile: videoInfo.previewfile } | ||
231 | |||
232 | // Don't need the meta file anymore | ||
233 | await deleteResumableUploadMetaFile(videoPhysicalFile.path) | ||
234 | |||
235 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
236 | } | ||
237 | |||
238 | async function addVideo (options: { | ||
239 | res: express.Response | ||
240 | videoPhysicalFile: express.VideoUploadFile | ||
241 | videoInfo: VideoCreate | ||
242 | files: express.UploadFiles | ||
243 | }) { | ||
244 | const { res, videoPhysicalFile, videoInfo, files } = options | ||
245 | const videoChannel = res.locals.videoChannel | ||
246 | const user = res.locals.oauth.token.User | ||
247 | |||
248 | const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id) | ||
249 | |||
250 | videoData.state = CONFIG.TRANSCODING.ENABLED | ||
251 | ? VideoState.TO_TRANSCODE | ||
252 | : VideoState.PUBLISHED | ||
253 | |||
254 | videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware | ||
190 | 255 | ||
191 | const video = new VideoModel(videoData) as MVideoFullLight | 256 | const video = new VideoModel(videoData) as MVideoFullLight |
192 | video.VideoChannel = res.locals.videoChannel | 257 | video.VideoChannel = videoChannel |
193 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 258 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
194 | 259 | ||
195 | const videoFile = new VideoFileModel({ | 260 | const videoFile = new VideoFileModel({ |
@@ -217,7 +282,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
217 | 282 | ||
218 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ | 283 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ |
219 | video, | 284 | video, |
220 | files: req.files, | 285 | files, |
221 | fallback: type => generateVideoMiniature({ video, videoFile, type }) | 286 | fallback: type => generateVideoMiniature({ video, videoFile, type }) |
222 | }) | 287 | }) |
223 | 288 | ||
@@ -248,9 +313,12 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
248 | }, { transaction: t }) | 313 | }, { transaction: t }) |
249 | } | 314 | } |
250 | 315 | ||
316 | // Channel has a new content, set as updated | ||
317 | await videoCreated.VideoChannel.setAsUpdated(t) | ||
318 | |||
251 | await autoBlacklistVideoIfNeeded({ | 319 | await autoBlacklistVideoIfNeeded({ |
252 | video, | 320 | video, |
253 | user: res.locals.oauth.token.User, | 321 | user, |
254 | isRemote: false, | 322 | isRemote: false, |
255 | isNew: true, | 323 | isNew: true, |
256 | transaction: t | 324 | transaction: t |
@@ -279,7 +347,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
279 | .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) })) | 347 | .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) })) |
280 | 348 | ||
281 | if (video.state === VideoState.TO_TRANSCODE) { | 349 | if (video.state === VideoState.TO_TRANSCODE) { |
282 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User) | 350 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, user) |
283 | } | 351 | } |
284 | 352 | ||
285 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) | 353 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 921067e65..f0717bbbc 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -167,7 +167,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
167 | videoChannelId: videoChannel ? videoChannel.id : null | 167 | videoChannelId: videoChannel ? videoChannel.id : null |
168 | } | 168 | } |
169 | 169 | ||
170 | const resultList = await VideoModel.listForApi({ | 170 | const { data } = await VideoModel.listForApi({ |
171 | start, | 171 | start, |
172 | count: FEEDS.COUNT, | 172 | count: FEEDS.COUNT, |
173 | sort: req.query.sort, | 173 | sort: req.query.sort, |
@@ -175,10 +175,11 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
175 | nsfw, | 175 | nsfw, |
176 | filter: req.query.filter as VideoFilter, | 176 | filter: req.query.filter as VideoFilter, |
177 | withFiles: true, | 177 | withFiles: true, |
178 | countVideos: false, | ||
178 | ...options | 179 | ...options |
179 | }) | 180 | }) |
180 | 181 | ||
181 | addVideosToFeed(feed, resultList.data) | 182 | addVideosToFeed(feed, data) |
182 | 183 | ||
183 | // Now the feed generation is done, let's send it! | 184 | // Now the feed generation is done, let's send it! |
184 | return sendFeed(feed, req, res) | 185 | return sendFeed(feed, req, res) |
@@ -198,20 +199,22 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp | |||
198 | queryString: new URL(WEBSERVER.URL + req.url).search | 199 | queryString: new URL(WEBSERVER.URL + req.url).search |
199 | }) | 200 | }) |
200 | 201 | ||
201 | const resultList = await VideoModel.listForApi({ | 202 | const { data } = await VideoModel.listForApi({ |
202 | start, | 203 | start, |
203 | count: FEEDS.COUNT, | 204 | count: FEEDS.COUNT, |
204 | sort: req.query.sort, | 205 | sort: req.query.sort, |
205 | includeLocalVideos: false, | 206 | includeLocalVideos: false, |
206 | nsfw, | 207 | nsfw, |
207 | filter: req.query.filter as VideoFilter, | 208 | filter: req.query.filter as VideoFilter, |
209 | |||
208 | withFiles: true, | 210 | withFiles: true, |
211 | countVideos: false, | ||
209 | 212 | ||
210 | followerActorId: res.locals.user.Account.Actor.id, | 213 | followerActorId: res.locals.user.Account.Actor.id, |
211 | user: res.locals.user | 214 | user: res.locals.user |
212 | }) | 215 | }) |
213 | 216 | ||
214 | addVideosToFeed(feed, resultList.data) | 217 | addVideosToFeed(feed, data) |
215 | 218 | ||
216 | // Now the feed generation is done, let's send it! | 219 | // Now the feed generation is done, let's send it! |
217 | return sendFeed(feed, req, res) | 220 | return sendFeed(feed, req, res) |