Fix private video download
[oweals/peertube.git] / server / controllers / api / video-channel.ts
1 import * as express from 'express'
2 import { getFormattedObjects, getServerActor } from '../../helpers/utils'
3 import {
4   asyncMiddleware,
5   asyncRetryTransactionMiddleware,
6   authenticate,
7   commonVideosFiltersValidator,
8   optionalAuthenticate,
9   paginationValidator,
10   setDefaultPagination,
11   setDefaultSort,
12   videoChannelsAddValidator,
13   videoChannelsRemoveValidator,
14   videoChannelsSortValidator,
15   videoChannelsUpdateValidator,
16   videoPlaylistsSortValidator
17 } from '../../middlewares'
18 import { VideoChannelModel } from '../../models/video/video-channel'
19 import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
20 import { sendUpdateActor } from '../../lib/activitypub/send'
21 import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22 import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
23 import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
24 import { setAsyncActorKeys } from '../../lib/activitypub'
25 import { AccountModel } from '../../models/account/account'
26 import { MIMETYPES } from '../../initializers/constants'
27 import { logger } from '../../helpers/logger'
28 import { VideoModel } from '../../models/video/video'
29 import { updateAvatarValidator } from '../../middlewares/validators/avatar'
30 import { updateActorAvatarFile } from '../../lib/avatar'
31 import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
32 import { resetSequelizeInstance } from '../../helpers/database-utils'
33 import { JobQueue } from '../../lib/job-queue'
34 import { VideoPlaylistModel } from '../../models/video/video-playlist'
35 import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
36 import { CONFIG } from '../../initializers/config'
37 import { sequelizeTypescript } from '../../initializers/database'
38 import { MChannelAccountDefault } from '@server/typings/models'
39
40 const auditLogger = auditLoggerFactory('channels')
41 const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
42
43 const videoChannelRouter = express.Router()
44
45 videoChannelRouter.get('/',
46   paginationValidator,
47   videoChannelsSortValidator,
48   setDefaultSort,
49   setDefaultPagination,
50   asyncMiddleware(listVideoChannels)
51 )
52
53 videoChannelRouter.post('/',
54   authenticate,
55   asyncMiddleware(videoChannelsAddValidator),
56   asyncRetryTransactionMiddleware(addVideoChannel)
57 )
58
59 videoChannelRouter.post('/:nameWithHost/avatar/pick',
60   authenticate,
61   reqAvatarFile,
62   // Check the rights
63   asyncMiddleware(videoChannelsUpdateValidator),
64   updateAvatarValidator,
65   asyncMiddleware(updateVideoChannelAvatar)
66 )
67
68 videoChannelRouter.put('/:nameWithHost',
69   authenticate,
70   asyncMiddleware(videoChannelsUpdateValidator),
71   asyncRetryTransactionMiddleware(updateVideoChannel)
72 )
73
74 videoChannelRouter.delete('/:nameWithHost',
75   authenticate,
76   asyncMiddleware(videoChannelsRemoveValidator),
77   asyncRetryTransactionMiddleware(removeVideoChannel)
78 )
79
80 videoChannelRouter.get('/:nameWithHost',
81   asyncMiddleware(videoChannelsNameWithHostValidator),
82   asyncMiddleware(getVideoChannel)
83 )
84
85 videoChannelRouter.get('/:nameWithHost/video-playlists',
86   asyncMiddleware(videoChannelsNameWithHostValidator),
87   paginationValidator,
88   videoPlaylistsSortValidator,
89   setDefaultSort,
90   setDefaultPagination,
91   commonVideoPlaylistFiltersValidator,
92   asyncMiddleware(listVideoChannelPlaylists)
93 )
94
95 videoChannelRouter.get('/:nameWithHost/videos',
96   asyncMiddleware(videoChannelsNameWithHostValidator),
97   paginationValidator,
98   videosSortValidator,
99   setDefaultSort,
100   setDefaultPagination,
101   optionalAuthenticate,
102   commonVideosFiltersValidator,
103   asyncMiddleware(listVideoChannelVideos)
104 )
105
106 // ---------------------------------------------------------------------------
107
108 export {
109   videoChannelRouter
110 }
111
112 // ---------------------------------------------------------------------------
113
114 async function listVideoChannels (req: express.Request, res: express.Response) {
115   const serverActor = await getServerActor()
116   const resultList = await VideoChannelModel.listForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
117
118   return res.json(getFormattedObjects(resultList.data, resultList.total))
119 }
120
121 async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
122   const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
123   const videoChannel = res.locals.videoChannel
124   const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
125
126   const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel)
127
128   auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
129
130   return res
131     .json({
132       avatar: avatar.toFormattedJSON()
133     })
134     .end()
135 }
136
137 async function addVideoChannel (req: express.Request, res: express.Response) {
138   const videoChannelInfo: VideoChannelCreate = req.body
139
140   const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
141     const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
142
143     return createLocalVideoChannel(videoChannelInfo, account, t)
144   })
145
146   setAsyncActorKeys(videoChannelCreated.Actor)
147     .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.url, { err }))
148
149   auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON()))
150   logger.info('Video channel %s created.', videoChannelCreated.Actor.url)
151
152   return res.json({
153     videoChannel: {
154       id: videoChannelCreated.id
155     }
156   }).end()
157 }
158
159 async function updateVideoChannel (req: express.Request, res: express.Response) {
160   const videoChannelInstance = res.locals.videoChannel
161   const videoChannelFieldsSave = videoChannelInstance.toJSON()
162   const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
163   const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
164   let doBulkVideoUpdate = false
165
166   try {
167     await sequelizeTypescript.transaction(async t => {
168       const sequelizeOptions = {
169         transaction: t
170       }
171
172       if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
173       if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
174
175       if (videoChannelInfoToUpdate.support !== undefined) {
176         const oldSupportField = videoChannelInstance.support
177         videoChannelInstance.support = videoChannelInfoToUpdate.support
178
179         if (videoChannelInfoToUpdate.bulkVideosSupportUpdate === true && oldSupportField !== videoChannelInfoToUpdate.support) {
180           doBulkVideoUpdate = true
181           await VideoModel.bulkUpdateSupportField(videoChannelInstance, t)
182         }
183       }
184
185       const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault
186       await sendUpdateActor(videoChannelInstanceUpdated, t)
187
188       auditLogger.update(
189         getAuditIdFromRes(res),
190         new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
191         oldVideoChannelAuditKeys
192       )
193
194       logger.info('Video channel %s updated.', videoChannelInstance.Actor.url)
195     })
196   } catch (err) {
197     logger.debug('Cannot update the video channel.', { err })
198
199     // Force fields we want to update
200     // If the transaction is retried, sequelize will think the object has not changed
201     // So it will skip the SQL request, even if the last one was ROLLBACKed!
202     resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
203
204     throw err
205   }
206
207   res.type('json').status(204).end()
208
209   // Don't process in a transaction, and after the response because it could be long
210   if (doBulkVideoUpdate) {
211     await federateAllVideosOfChannel(videoChannelInstance)
212   }
213 }
214
215 async function removeVideoChannel (req: express.Request, res: express.Response) {
216   const videoChannelInstance = res.locals.videoChannel
217
218   await sequelizeTypescript.transaction(async t => {
219     await VideoPlaylistModel.resetPlaylistsOfChannel(videoChannelInstance.id, t)
220
221     await videoChannelInstance.destroy({ transaction: t })
222
223     auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
224     logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url)
225   })
226
227   return res.type('json').status(204).end()
228 }
229
230 async function getVideoChannel (req: express.Request, res: express.Response) {
231   const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
232
233   if (videoChannelWithVideos.isOutdated()) {
234     JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
235             .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
236   }
237
238   return res.json(videoChannelWithVideos.toFormattedJSON())
239 }
240
241 async function listVideoChannelPlaylists (req: express.Request, res: express.Response) {
242   const serverActor = await getServerActor()
243
244   const resultList = await VideoPlaylistModel.listForApi({
245     followerActorId: serverActor.id,
246     start: req.query.start,
247     count: req.query.count,
248     sort: req.query.sort,
249     videoChannelId: res.locals.videoChannel.id,
250     type: req.query.playlistType
251   })
252
253   return res.json(getFormattedObjects(resultList.data, resultList.total))
254 }
255
256 async function listVideoChannelVideos (req: express.Request, res: express.Response) {
257   const videoChannelInstance = res.locals.videoChannel
258   const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
259
260   const resultList = await VideoModel.listForApi({
261     followerActorId,
262     start: req.query.start,
263     count: req.query.count,
264     sort: req.query.sort,
265     includeLocalVideos: true,
266     categoryOneOf: req.query.categoryOneOf,
267     licenceOneOf: req.query.licenceOneOf,
268     languageOneOf: req.query.languageOneOf,
269     tagsOneOf: req.query.tagsOneOf,
270     tagsAllOf: req.query.tagsAllOf,
271     filter: req.query.filter,
272     nsfw: buildNSFWFilter(res, req.query.nsfw),
273     withFiles: false,
274     videoChannelId: videoChannelInstance.id,
275     user: res.locals.oauth ? res.locals.oauth.token.User : undefined
276   })
277
278   return res.json(getFormattedObjects(resultList.data, resultList.total))
279 }