aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/video-channel.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/video-channel.ts')
-rw-r--r--server/controllers/api/video-channel.ts431
1 files changed, 0 insertions, 431 deletions
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
deleted file mode 100644
index 18de5bf6a..000000000
--- a/server/controllers/api/video-channel.ts
+++ /dev/null
@@ -1,431 +0,0 @@
1import express from 'express'
2import { pickCommonVideoQuery } from '@server/helpers/query'
3import { Hooks } from '@server/lib/plugins/hooks'
4import { ActorFollowModel } from '@server/models/actor/actor-follow'
5import { getServerActor } from '@server/models/application/application'
6import { MChannelBannerAccountDefault } from '@server/types/models'
7import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models'
8import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
9import { resetSequelizeInstance } from '../../helpers/database-utils'
10import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
11import { logger } from '../../helpers/logger'
12import { getFormattedObjects } from '../../helpers/utils'
13import { MIMETYPES } from '../../initializers/constants'
14import { sequelizeTypescript } from '../../initializers/database'
15import { sendUpdateActor } from '../../lib/activitypub/send'
16import { JobQueue } from '../../lib/job-queue'
17import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../lib/local-actor'
18import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
19import {
20 apiRateLimiter,
21 asyncMiddleware,
22 asyncRetryTransactionMiddleware,
23 authenticate,
24 commonVideosFiltersValidator,
25 ensureCanManageChannelOrAccount,
26 optionalAuthenticate,
27 paginationValidator,
28 setDefaultPagination,
29 setDefaultSort,
30 setDefaultVideosSort,
31 videoChannelsAddValidator,
32 videoChannelsRemoveValidator,
33 videoChannelsSortValidator,
34 videoChannelsUpdateValidator,
35 videoPlaylistsSortValidator
36} from '../../middlewares'
37import {
38 ensureChannelOwnerCanUpload,
39 ensureIsLocalChannel,
40 videoChannelImportVideosValidator,
41 videoChannelsFollowersSortValidator,
42 videoChannelsListValidator,
43 videoChannelsNameWithHostValidator,
44 videosSortValidator
45} from '../../middlewares/validators'
46import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image'
47import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
48import { AccountModel } from '../../models/account/account'
49import { guessAdditionalAttributesFromQuery } from '../../models/video/formatter'
50import { VideoModel } from '../../models/video/video'
51import { VideoChannelModel } from '../../models/video/video-channel'
52import { VideoPlaylistModel } from '../../models/video/video-playlist'
53
54const auditLogger = auditLoggerFactory('channels')
55const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT)
56const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT)
57
58const videoChannelRouter = express.Router()
59
60videoChannelRouter.use(apiRateLimiter)
61
62videoChannelRouter.get('/',
63 paginationValidator,
64 videoChannelsSortValidator,
65 setDefaultSort,
66 setDefaultPagination,
67 videoChannelsListValidator,
68 asyncMiddleware(listVideoChannels)
69)
70
71videoChannelRouter.post('/',
72 authenticate,
73 asyncMiddleware(videoChannelsAddValidator),
74 asyncRetryTransactionMiddleware(addVideoChannel)
75)
76
77videoChannelRouter.post('/:nameWithHost/avatar/pick',
78 authenticate,
79 reqAvatarFile,
80 asyncMiddleware(videoChannelsNameWithHostValidator),
81 ensureIsLocalChannel,
82 ensureCanManageChannelOrAccount,
83 updateAvatarValidator,
84 asyncMiddleware(updateVideoChannelAvatar)
85)
86
87videoChannelRouter.post('/:nameWithHost/banner/pick',
88 authenticate,
89 reqBannerFile,
90 asyncMiddleware(videoChannelsNameWithHostValidator),
91 ensureIsLocalChannel,
92 ensureCanManageChannelOrAccount,
93 updateBannerValidator,
94 asyncMiddleware(updateVideoChannelBanner)
95)
96
97videoChannelRouter.delete('/:nameWithHost/avatar',
98 authenticate,
99 asyncMiddleware(videoChannelsNameWithHostValidator),
100 ensureIsLocalChannel,
101 ensureCanManageChannelOrAccount,
102 asyncMiddleware(deleteVideoChannelAvatar)
103)
104
105videoChannelRouter.delete('/:nameWithHost/banner',
106 authenticate,
107 asyncMiddleware(videoChannelsNameWithHostValidator),
108 ensureIsLocalChannel,
109 ensureCanManageChannelOrAccount,
110 asyncMiddleware(deleteVideoChannelBanner)
111)
112
113videoChannelRouter.put('/:nameWithHost',
114 authenticate,
115 asyncMiddleware(videoChannelsNameWithHostValidator),
116 ensureIsLocalChannel,
117 ensureCanManageChannelOrAccount,
118 videoChannelsUpdateValidator,
119 asyncRetryTransactionMiddleware(updateVideoChannel)
120)
121
122videoChannelRouter.delete('/:nameWithHost',
123 authenticate,
124 asyncMiddleware(videoChannelsNameWithHostValidator),
125 ensureIsLocalChannel,
126 ensureCanManageChannelOrAccount,
127 asyncMiddleware(videoChannelsRemoveValidator),
128 asyncRetryTransactionMiddleware(removeVideoChannel)
129)
130
131videoChannelRouter.get('/:nameWithHost',
132 asyncMiddleware(videoChannelsNameWithHostValidator),
133 asyncMiddleware(getVideoChannel)
134)
135
136videoChannelRouter.get('/:nameWithHost/video-playlists',
137 asyncMiddleware(videoChannelsNameWithHostValidator),
138 paginationValidator,
139 videoPlaylistsSortValidator,
140 setDefaultSort,
141 setDefaultPagination,
142 commonVideoPlaylistFiltersValidator,
143 asyncMiddleware(listVideoChannelPlaylists)
144)
145
146videoChannelRouter.get('/:nameWithHost/videos',
147 asyncMiddleware(videoChannelsNameWithHostValidator),
148 paginationValidator,
149 videosSortValidator,
150 setDefaultVideosSort,
151 setDefaultPagination,
152 optionalAuthenticate,
153 commonVideosFiltersValidator,
154 asyncMiddleware(listVideoChannelVideos)
155)
156
157videoChannelRouter.get('/:nameWithHost/followers',
158 authenticate,
159 asyncMiddleware(videoChannelsNameWithHostValidator),
160 ensureCanManageChannelOrAccount,
161 paginationValidator,
162 videoChannelsFollowersSortValidator,
163 setDefaultSort,
164 setDefaultPagination,
165 asyncMiddleware(listVideoChannelFollowers)
166)
167
168videoChannelRouter.post('/:nameWithHost/import-videos',
169 authenticate,
170 asyncMiddleware(videoChannelsNameWithHostValidator),
171 asyncMiddleware(videoChannelImportVideosValidator),
172 ensureIsLocalChannel,
173 ensureCanManageChannelOrAccount,
174 asyncMiddleware(ensureChannelOwnerCanUpload),
175 asyncMiddleware(importVideosInChannel)
176)
177
178// ---------------------------------------------------------------------------
179
180export {
181 videoChannelRouter
182}
183
184// ---------------------------------------------------------------------------
185
186async function listVideoChannels (req: express.Request, res: express.Response) {
187 const serverActor = await getServerActor()
188
189 const apiOptions = await Hooks.wrapObject({
190 actorId: serverActor.id,
191 start: req.query.start,
192 count: req.query.count,
193 sort: req.query.sort
194 }, 'filter:api.video-channels.list.params')
195
196 const resultList = await Hooks.wrapPromiseFun(
197 VideoChannelModel.listForApi,
198 apiOptions,
199 'filter:api.video-channels.list.result'
200 )
201
202 return res.json(getFormattedObjects(resultList.data, resultList.total))
203}
204
205async function updateVideoChannelBanner (req: express.Request, res: express.Response) {
206 const bannerPhysicalFile = req.files['bannerfile'][0]
207 const videoChannel = res.locals.videoChannel
208 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
209
210 const banners = await updateLocalActorImageFiles(videoChannel, bannerPhysicalFile, ActorImageType.BANNER)
211
212 auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
213
214 return res.json({
215 banners: banners.map(b => b.toFormattedJSON())
216 })
217}
218
219async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
220 const avatarPhysicalFile = req.files['avatarfile'][0]
221 const videoChannel = res.locals.videoChannel
222 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
223
224 const avatars = await updateLocalActorImageFiles(videoChannel, avatarPhysicalFile, ActorImageType.AVATAR)
225 auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
226
227 return res.json({
228 avatars: avatars.map(a => a.toFormattedJSON())
229 })
230}
231
232async function deleteVideoChannelAvatar (req: express.Request, res: express.Response) {
233 const videoChannel = res.locals.videoChannel
234
235 await deleteLocalActorImageFile(videoChannel, ActorImageType.AVATAR)
236
237 return res.status(HttpStatusCode.NO_CONTENT_204).end()
238}
239
240async function deleteVideoChannelBanner (req: express.Request, res: express.Response) {
241 const videoChannel = res.locals.videoChannel
242
243 await deleteLocalActorImageFile(videoChannel, ActorImageType.BANNER)
244
245 return res.status(HttpStatusCode.NO_CONTENT_204).end()
246}
247
248async function addVideoChannel (req: express.Request, res: express.Response) {
249 const videoChannelInfo: VideoChannelCreate = req.body
250
251 const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
252 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
253
254 return createLocalVideoChannel(videoChannelInfo, account, t)
255 })
256
257 const payload = { actorId: videoChannelCreated.actorId }
258 await JobQueue.Instance.createJob({ type: 'actor-keys', payload })
259
260 auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON()))
261 logger.info('Video channel %s created.', videoChannelCreated.Actor.url)
262
263 Hooks.runAction('action:api.video-channel.created', { videoChannel: videoChannelCreated, req, res })
264
265 return res.json({
266 videoChannel: {
267 id: videoChannelCreated.id
268 }
269 })
270}
271
272async function updateVideoChannel (req: express.Request, res: express.Response) {
273 const videoChannelInstance = res.locals.videoChannel
274 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
275 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
276 let doBulkVideoUpdate = false
277
278 try {
279 await sequelizeTypescript.transaction(async t => {
280 if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
281 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
282
283 if (videoChannelInfoToUpdate.support !== undefined) {
284 const oldSupportField = videoChannelInstance.support
285 videoChannelInstance.support = videoChannelInfoToUpdate.support
286
287 if (videoChannelInfoToUpdate.bulkVideosSupportUpdate === true && oldSupportField !== videoChannelInfoToUpdate.support) {
288 doBulkVideoUpdate = true
289 await VideoModel.bulkUpdateSupportField(videoChannelInstance, t)
290 }
291 }
292
293 const videoChannelInstanceUpdated = await videoChannelInstance.save({ transaction: t }) as MChannelBannerAccountDefault
294 await sendUpdateActor(videoChannelInstanceUpdated, t)
295
296 auditLogger.update(
297 getAuditIdFromRes(res),
298 new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
299 oldVideoChannelAuditKeys
300 )
301
302 Hooks.runAction('action:api.video-channel.updated', { videoChannel: videoChannelInstanceUpdated, req, res })
303
304 logger.info('Video channel %s updated.', videoChannelInstance.Actor.url)
305 })
306 } catch (err) {
307 logger.debug('Cannot update the video channel.', { err })
308
309 // If the transaction is retried, sequelize will think the object has not changed
310 // So we need to restore the previous fields
311 await resetSequelizeInstance(videoChannelInstance)
312
313 throw err
314 }
315
316 res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
317
318 // Don't process in a transaction, and after the response because it could be long
319 if (doBulkVideoUpdate) {
320 await federateAllVideosOfChannel(videoChannelInstance)
321 }
322}
323
324async function removeVideoChannel (req: express.Request, res: express.Response) {
325 const videoChannelInstance = res.locals.videoChannel
326
327 await sequelizeTypescript.transaction(async t => {
328 await VideoPlaylistModel.resetPlaylistsOfChannel(videoChannelInstance.id, t)
329
330 await videoChannelInstance.destroy({ transaction: t })
331
332 Hooks.runAction('action:api.video-channel.deleted', { videoChannel: videoChannelInstance, req, res })
333
334 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
335 logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url)
336 })
337
338 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
339}
340
341async function getVideoChannel (req: express.Request, res: express.Response) {
342 const id = res.locals.videoChannel.id
343 const videoChannel = await Hooks.wrapObject(res.locals.videoChannel, 'filter:api.video-channel.get.result', { id })
344
345 if (videoChannel.isOutdated()) {
346 JobQueue.Instance.createJobAsync({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannel.Actor.url } })
347 }
348
349 return res.json(videoChannel.toFormattedJSON())
350}
351
352async function listVideoChannelPlaylists (req: express.Request, res: express.Response) {
353 const serverActor = await getServerActor()
354
355 const resultList = await VideoPlaylistModel.listForApi({
356 followerActorId: serverActor.id,
357 start: req.query.start,
358 count: req.query.count,
359 sort: req.query.sort,
360 videoChannelId: res.locals.videoChannel.id,
361 type: req.query.playlistType
362 })
363
364 return res.json(getFormattedObjects(resultList.data, resultList.total))
365}
366
367async function listVideoChannelVideos (req: express.Request, res: express.Response) {
368 const serverActor = await getServerActor()
369
370 const videoChannelInstance = res.locals.videoChannel
371
372 const displayOnlyForFollower = isUserAbleToSearchRemoteURI(res)
373 ? null
374 : {
375 actorId: serverActor.id,
376 orLocalVideos: true
377 }
378
379 const countVideos = getCountVideos(req)
380 const query = pickCommonVideoQuery(req.query)
381
382 const apiOptions = await Hooks.wrapObject({
383 ...query,
384
385 displayOnlyForFollower,
386 nsfw: buildNSFWFilter(res, query.nsfw),
387 videoChannelId: videoChannelInstance.id,
388 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
389 countVideos
390 }, 'filter:api.video-channels.videos.list.params')
391
392 const resultList = await Hooks.wrapPromiseFun(
393 VideoModel.listForApi,
394 apiOptions,
395 'filter:api.video-channels.videos.list.result'
396 )
397
398 return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
399}
400
401async function listVideoChannelFollowers (req: express.Request, res: express.Response) {
402 const channel = res.locals.videoChannel
403
404 const resultList = await ActorFollowModel.listFollowersForApi({
405 actorIds: [ channel.actorId ],
406 start: req.query.start,
407 count: req.query.count,
408 sort: req.query.sort,
409 search: req.query.search,
410 state: 'accepted'
411 })
412
413 return res.json(getFormattedObjects(resultList.data, resultList.total))
414}
415
416async function importVideosInChannel (req: express.Request, res: express.Response) {
417 const { externalChannelUrl } = req.body as VideosImportInChannelCreate
418
419 await JobQueue.Instance.createJob({
420 type: 'video-channel-import',
421 payload: {
422 externalChannelUrl,
423 videoChannelId: res.locals.videoChannel.id,
424 partOfChannelSyncId: res.locals.videoChannelSync?.id
425 }
426 })
427
428 logger.info('Video import job for channel "%s" with url "%s" created.', res.locals.videoChannel.name, externalChannelUrl)
429
430 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
431}